Philip Withnall
philip.withnall@collabora.co.uk 2015
테스트할 프로그램을 설계하고 프로그램에 맞는 단위 테스트를 작성합니다
조성호
shcho@gnome.org 2016-2018.

# 단위 테스트

#

        요약

        단위 테스트는 상당한 양의 작성 코드를 테스트하는 근본 방식이어야 하는데, 단위 테스트는 한번 작성하고 여러번 실행하기 때문입니다. 직접 작성한 테스트 코드는 한번 계획하고 직접 여러번 실행해야합니다.

        단위 테스트 개발은 테스트할 코드의 구조와 API 설계로 시작합니다. 코드는 쉽게 테스트 할 수 있도록 설계해야 합니다. 그렇지 않으면 잠재적으로 테스트하기 매우 어려워집니다.

          단위 테스트는 최대한 작게 작성하지만, 더 작게 작성하지 않습니다().
          코드 범위를 최대한 할당하는 테스트를 작성하려면 코드 범위 도구를 사용하십시오().
          메모리 누수 현상 및 다른 문제를 확인하려면 모든 단위 테스트를 Valgrind에서 실행하십시오().
          가능하다면 단위 테스트를 자동으로 만드는 적당한 도구를 사용하십시오().
          시작할 때부터 테스트할 수 있는 코드로 설계하십시오().

# 단위 테스트 코드 작성

테스트를 실행하면서 코드 영역에서 수집하는 정보를 찾으면서 단위 테스트를 작성해야합니다. 보통 단위 테스트 초기 집합을 작성하고, 실행하여 해당 범위의 데이터를 얻어내며, 코드 영역 수준을 넓혀가며 다시 작업 함을 의미합니다. 영역은 우선적으로 (최소한 일부 영역에서) 모든 함수를 다루는지 확인하고, 코드의 모든 줄을 다루는지 확인하여 영역을 넓혀야합니다. 우선적으로 함수를 다루면, 영향을 주는 테스트를 방해하는 API의 문제를 빨리 찾아낼 수 있습니다. 이 방식으로 단위 테스트에서 쉽게 호출할 수 없는 자체 함수를 나타냅니다. 총체적으로 말하자면, 90% 이상의 범위 수준을 대상으로 해야 합니다. 프로젝트 요구사항에서 다루는 케이스만 테스트하지 마십시오. 모두 테스트하십시오.

git 커밋과 마찬가지로, 단일 개별 API또는 동작을 테스트할 때, 각각의 단위 테스트는 ‘가능한 한 작게, 그러나 더 작지는 않’아야합니다. 각 테스트 케이스는 기타 테스트 케이스의 상태와는 상관 없이 개별적으로 실행할 수 있어야합니다. 다른 모든 테스트 코드와 마찬가지로 어떤 절차 없이 단일 실패 테스트를 디버깅할 수 있게 하려면 이 점이 중요합니다. 또한, 단일 테스트 실패로 하여금 상투적으로 ‘어딘가에서 단위 테스트에 실패했습니다’ 같은 메시지를 출력하기보단, 개별 API를 역추적할 수 있다는 의미이기도 합니다.

GLib은 그룹, 계층별로 테스트를 수행할 수 있는 GTest 프레임워크 단위 테스트를 지원합니다. ./test-suite-name -p /path/to/test/group 명령과 같이 테스트 이진 파일에 -p 인자를 추가하여 고급 디버깅을 목적으로 관련 그룹 테스트를 함께 실행할 수 있음을 의미합니다.

# 설치한 테스트

모든 단위 테스트는 installed-tests 표준에 따라, 시스템 영역에 걸쳐 설치해야 합니다.

단위 테스트를 설치하면, 단위 통합(CI)이 쉬워지는데, CI 환경에서 어떤 프로젝트의 테스트는 다른 프로젝트로 바뀌고 나서도 다시 실행할 수 있기에 모듈간 인터페이스를 테스트합니다. 이는 그놈처럼 프로젝트간 강결합을 이룬 상황에 유용합니다.

installed-tests 지원을 추가하려면 configure.ac에 다음 내용을 추가하십시오:

# Installed tests AC_ARG_ENABLE([modular_tests], AS_HELP_STRING([--disable-modular-tests], [Disable build of test programs (default: no)]),, [enable_modular_tests=yes]) AC_ARG_ENABLE([installed_tests], AS_HELP_STRING([--enable-installed-tests], [Install test programs (default: no)]),, [enable_installed_tests=no]) AM_CONDITIONAL([BUILD_MODULAR_TESTS], [test "$enable_modular_tests" = "yes" || test "$enable_installed_tests" = "yes"]) AM_CONDITIONAL([BUILDOPT_INSTALL_TESTS],[test "$enable_installed_tests" = "yes"])

그 다음 tests/Makefile.am에 다음 내용을 추가하십시오:

insttestdir = $(libexecdir)/installed-tests/[project]

all_test_programs = \ test-program1 \ test-program2 \ test-program3 \ $(NULL) if BUILD_MODULAR_TESTS TESTS = $(all_test_programs) noinst_PROGRAMS = $(TESTS) endif

if BUILDOPT_INSTALL_TESTS insttest_PROGRAMS = $(all_test_programs)

testmetadir = $(datadir)/installed-tests/[project] testmeta_DATA = $(all_test_programs:=.test)

testdatadir = $(insttestdir) testdata_DATA = $(test_files)

testdata_SCRIPTS = $(test_script_files) endif

EXTRA_DIST = $(test_files)

%.test: % Makefile $(AM_V_GEN) (echo '[Test]' > $@.tmp; \ echo 'Type=session' >> $@.tmp; \ echo 'Exec=$(insttestdir)/$<' >> $@.tmp; \ mv $@.tmp $@)

# 메모리 누수 검사

광범위한 코드 영역을 다루는 단위 테스트를 작성하고 나면, Valgrind와 같이 메모리 누수, 스레드 오류, 할당 문제 등 전체 코드 영역을 검사하는 다양한 동적 분석 도구에서 실행할 수 있습니다. 단위 테스트에서 다루는 코드 영역이 더 커지면, Valgrind 결과에 대해 더한 신뢰감이 있을 수 있습니다. 자세히 알아보려면 빌드 시스템 통합 방식 내용이 함께 들어있는 을 참고하십시오.

진지하게 말하자면, 단위 테스트에서 메모리나 기타 자원이 정리가 되지 않는 현상이 있어서는 안되며, 이와 비슷하게 스레드 처리 문제가 있어서도 안된다는 의미입니다. 어떤 문제의 경우 실제 프로젝트 코드를 분석할 때 거짓양성이 나타날 수 있습니다(거짓 양성은 굳이 단위 테스트를 수정하여 문제를 고칠 필요가 없습니다).

# 테스트 만들기

Certain types of code are quite repetitive, and require a lot of unit tests to gain good coverage; but are appropriate for test data generation, where a tool is used to automatically generate test vectors for the code. This can drastically reduce the time needed for writing unit tests, for code in these specific domains.

## JSON

데이터 생성 시험시 도메인을 잘 이해할 수 있는 예제는 규정된 양식(schema)을 따라 데이터를 해석하는 파싱을 들 수 있습니다. XML과 JSON 문서의 경우를 이야기합니다. Walbottle과 같은 JSON 도구는 양식(schema)에 따라 올바른 또는 올바르지 않은 모든 형식의 테스트 벡터를 만들 때 사용할 수 있습니다.

JSON 문서의 모든 형식은, 테스트 벡터를 만드는 Warbottle에 전달할 수 있는 JSON 문서 형식을 정의하는 JSON 양식(schema)이 있습니다:

json-schema-generate --valid-only schema.json json-schema-generate --invalid-only schema.json

이 테스트 벡터는 단위 테스트에서 테스트하는 코드에 전달할 수 있습니다. --valid-only로 만든 JSON 인스턴스는 테스트를 통과해야합니다. 반면에 --invalid-only로 만든 JSON 인스턴스는 테스트를 통과하지 말아야합니다.

# 테스트 가능 코드 작성

코드는 API 설계 및 아키텍처에 근본적인 방식으로 영향을 주므로 설계 단계 때부터 테스트가 가능하도록 작성해야합니다. 몇가지 핵심 원칙이 있습니다:

전역 상태를 활용하지 마십시오. 싱글톤 객체는 단위 테스트를 수행할 때 따로 초기화하고 다룰 수 없기에 보통 좋지 않습니다.

데이터베이스, 네트워크, 파일 시스템의 외부 상태를 활용하는 경우 따로 분리하십시오. 이렇게 해야 단위 테스트에서 가정 객체로 외부 상태에 접근하도록 할 수 있습니다. 일반적 접근 방식으로는 테스트를 진행하는 동안 코드에 파일 시스템 래퍼 객체를 전달할 때 독립 인젝션 기법을 활용합니다. 예를 들어 클래스는 전역 데이터베이스(파일 시스템의 고정 지정 위치)를 불러오면 안되는데, 단위 테스트 수행 시 동작중인 시스템의 데이터베이스 사본을 덮어쓸 가능성이 있기 대문이며, 때문에 동시에 실행할 수 없습니다. 객체를 전달하려면 제공 데이터베이스 인터페이스로 해야 하는데, 실 운용 시스템에서는 데이터베이스 API의 씬 래퍼가 되겠습니다. 테스트를 할 때는, 객체 자신을 검사하며, 다양한 테스트에 대해 하드코딩 요청한 결과를 반환하는 모크 객체가 됩니다.

범용적으로 쓸 만한 유틸리티 함수는 노출하십시오.

최소한의 연결 코드를 전체 실행 프로그램으로 링크하게 하여 제각각 테스트할 수 있게 작은, 개별 라이브러리 모음으로 프로젝트를 나누십시오.

# 외부 링크

아래에 프로그램 테스트 가능성 주제를 다룬 글을 모아두었습니다:

Design for testability

Software testability

Dependency injection

Software design for testing