단위 테스트
단위 테스트 (Unit Tests)
모듈이 제대로 동작하는지 검증하는 테스트를 언어 차원에서 기본 제공해 주는 프레임워크가 바로 D의 단위 테스트예요. 보통 코드가 맞는지 확인하려고 별도 테스트 도구를 붙이곤 하는데, D는 unittest 블록이라는 걸 언어가 직접 지원해서 코드 옆에 바로 테스트를 붙여 둘 수 있죠. 활성화할 수도, 끌 수도 있는 구조라서 개발 중엔 켜고, 배포할 땐 끄면 돼요.
본문
단위 테스트는 모듈에 적용해서 모듈이 제대로 동작하는지 판단하는, 테스트 케이스로 이루어진 기본 제공 프레임워크입니다. D 프로그램은 단위 테스트를 켜거나 끈 상태로 실행할 수 있어요.
단위 테스트는 다음과 같이 정의하는 특수한 함수입니다.
unittest
{
...test code...
}
개별 테스트는 단위 테스트 안에서 AssertExpression으로 지정해요. 다른 곳에서 쓰는 AssertExpression과 달리, 여기의 assert는 항상 성립한다고 가정하지 않으며, assert가 실패해도 프로그램은 여전히 정의된 상태(defined state)를 유지합니다.
모듈에는 단위 테스트 함수가 몇 개든 있을 수 있어요. struct, union, class 선언 안에도 넣을 수 있고, 이들은 어휘 순서(lexical order)대로 실행됩니다.
단위 테스트가 활성화되면 모든 정적 초기화(static initialization)가 끝난 뒤, main() 함수가 호출되기 전에 실행됩니다.
예를 들어 두 값을 더하는 데 쓰는 클래스 Sum이 있다면, 단위 테스트를 이렇게 붙여 둘 수 있어요:
class Sum
{
int add(int x, int y) { return x + y; }
unittest
{
Sum sum = new Sum;
assert(sum.add(3,4) == 7);
assert(sum.add(-2,0) == -2);
}
}
단위 테스트가 활성화되어 있으면 version 식별자 unittest가 미리 정의됩니다.
속성이 붙은 단위 테스트 (Attributed Unittests)
단위 테스트에는 전역 함수 속성(global function attributes) 중 아무거나 붙일 수 있어요. 이런 단위 테스트는 템플릿 함수에 특정 속성이 붙어 있는지 검증할 때 특히 유용합니다:
void myFunc(T)(T[] data)
{
if (data.length > 2)
data[0] = data[1];
}
@safe nothrow unittest
{
auto arr = [1,2,3];
myFunc(arr);
assert(arr == [2,2,3]);
}
이 단위 테스트는 myFunc가 @safe, nothrow 코드만 포함하는지 검증해요. 같은 효과를 내려고 myFunc 자체에 이 속성들을 붙일 수도 있겠지만, 그러면 myFunc를 opAssign 메서드나 myFunc가 호출하는 다른 메서드에 @system 코드나 예외를 던지는(throwing) 코드가 있는 타입 T로는 인스턴스화할 수 없게 돼요. 위 관용구(idiom)는 그런 타입으로도 myFunc를 인스턴스화하면서, 동시에 myFunc 내부 코드 자체가 @system 동작이나 예외를 던지는 동작을 도입하지 않는다는 것만 검증할 수 있게 해 줍니다.
구현 정의 사항 (Implementation Defined):
- 단위 테스트가 활성화되어 있지 않으면, 구현은 UnitTest의 문법적·의미적 정확성을 검사할 의무가 없어요. 큰 단위 테스트 구간이 컴파일 시간에 미치는 영향을 줄이기 위해서지요. 토큰은 여전히 유효해야 하고, 구현은
{와}토큰을 세는 것만으로 UnitTest의 BlockStatement 끝을 찾을 수 있습니다. - 단위 테스트 결과를 사용자에게 보여 주는 방식.
- 단위 테스트를 켜거나 끄는 방법.
<b>-unittest</b>같은 컴파일러 스위치를 사용해 활성화하는 방식을 권장해요. - 모듈들이 자기 단위 테스트를 실행하도록 호출되는 순서.
- 프로그램이 첫 단위 테스트 실패에서 멈출지, 아니면 단위 테스트를 계속 실행할지.
권장 사항 (Best Practices):
- 커버리지 테스트(
<b>-cov</b>같은 것)와 단위 테스트를 함께 쓰는 게 효과적입니다. - 함수의 단위 테스트는 그 함수 바로 뒤에 두어요.
문서화된 단위 테스트 (Documented Unittests)
문서화된 단위 테스트(documented unittests)는 개발자가 사용자에게 코드 예제를 전달하면서, 동시에 그 예제가 유효함을 자동으로 검증하게 해 줍니다. 이렇게 하면 어떤 코드에 대해 문서가 낡아 버리는 문제가 자주 생기는 걸 피할 수 있어요.
선언 다음에 문서화된 단위 테스트가 이어지면, 그 단위 테스트 안의 코드가 선언의 example 섹션에 들어갑니다:
/// Math class
class Math
{
/// add function
static int add(int x, int y) { return x + y; }
///
unittest
{
assert(add(2, 2) == 4);
}
}
///
unittest
{
auto math = new Math();
auto result = math.add(2, 2);
}
위 코드는 다음 문서를 만들어 냅니다.
class Math;
Math class
Example:
auto math = new Math;
auto result = math.add(2, 2);
int add(int x, int y);
add function
Example:
assert(add(2, 2) == 4);
문서화되지 않은 단위 테스트나 private으로 표시된 단위 테스트는 코드 샘플을 만드는 데 쓰이지 않아요.
문서화된 단위 테스트는 여러 개일 수 있고 어떤 순서로든 올 수 있습니다. 이들은 마지막 비단위 테스트(non-unittest) 선언에 붙게 돼요:
/// add function
int add(int x, int y) { return x + y; }
/// code sample generated
unittest
{
assert(add(1, 1) == 2);
}
/// code sample not generated because the unittest is private
private unittest
{
assert(add(2, 2) == 4);
}
unittest
{
/// code sample not generated because the unittest isn't documented
assert(add(3, 3) == 6);
}
/// code sample generated, even if it only includes comments (or is empty)
unittest
{
/** assert(add(4, 4) == 8); */
}
위 코드는 다음 문서를 만들어 냅니다.
int add(int x, int y);
add function
Examples:
code sample generated
assert(add(1, 1) == 2);
Examples:
code sample generated, even if it is empty or only includes comments
/** assert(add(4, 4) == 8); */
더 알아보기 (Learn more)
- D 언어 공식 사양 'Unit Tests' 원문: https://dlang.org/spec/unittest.html
- 문장(Statements) —
unittest몸체인 BlockStatement - 표현식(Expressions) — 단위 테스트의 assert 표현식
- 조건부 컴파일(Conditional Compilation) —
unittestversion 식별자 - 오류 처리(Error Handling)
- 가비지 컬렉션(Garbage Collection)