단위 테스트

단위 테스트 (Unit Tests)

단위 테스트(unittest)는 모듈이 올바르게 동작하는지 확인하기 위한 내장 테스트 프레임워크예요. D 프로그램은 단위 테스트를 켜거나 끄고 실행할 수 있고, 단위 테스트는 컴파일러 스위치(-unittest)로 켜요. 이 문서는 단위 테스트 작성 문법과 속성(attribute) 부여, 그리고 문서화된 단위 테스트(Documented Unittests)를 다루어요.

출처: Unit Tests

본문

단위 테스트는 모듈에 적용되는 테스트 케이스의 내장 프레임워크예요. D 프로그램은 단위 테스트를 활성화하거나 비활성화한 상태로 실행할 수 있어요.

단위 테스트는 다음과 같이 정의하는 특별한 함수예요:

UnitTest:
    unittest BlockStatement
unittest
{
    ...test code...
}

개별 테스트는 단위 테스트 안에서 AssertExpression을 사용해 지정해요. 다른 곳에서 쓰는 AssertExpression과 달리, 여기서 assert는 참이라고 가정되지 않으며, assert가 실패해도 프로그램은 여전히 정의된 상태(definite 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);
    }
}

단위 테스트가 활성화되면 unittest 버전 식별자(version identifier)가 미리 정의돼요.

속성이 부여된 단위 테스트 (Attributed Unittests)

unittest에는 전역 함수 속성(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 자체에 붙여도 같은 결과를 얻을 수 있지만, 그렇게 하면 myFuncopAssign 메서드나 myFunc가 호출할 수 있는 다른 메서드에 @system이거나 던지는(throwing) 코드가 있는 타입 T로는 인스턴스화되지 못해요. 위의 관용구(idom)는 myFunc가 그런 타입으로 인스턴스화되는 것을 허용하면서도, 동시에 @system 및 던지는 동작이 myFunc 자체 내부 코드에 의해 도입되지 않음을 검증할 수 있어요.

Implementation Defined: (구현 정의 사항)

  • 단위 테스트가 활성화되지 않았다면, 구현은 UnitTest의 문법적·의미적 정확성을 검사할 필요가 없어요. 이는 더 큰 단위 테스트 구간의 컴파일 시간 영향을 줄이기 위함이에요. 토큰은 여전히 유효해야 하며, 구현은 단순히 {} 토큰을 세어서 UnitTest의 BlockStatement 끝을 찾을 수 있어요.
  • 사용자에게 단위 테스트 결과를 보여주는 방식.
  • 단위 테스트를 켜거나 끄는 방법. -unittest 같은 컴파일러 스위치를 사용해 활성화하는 것을 권장해요.
  • 모듈들이 자신의 단위 테스트를 실행하기 위해 호출되는 순서.
  • 프로그램이 첫 번째 단위 테스트 실패에서 멈출지, 아니면 나머지 단위 테스트를 계속 실행할지.

Best Practices: (모범 사례)

  • 단위 테스트를 커버리지 테스트(예: -cov)와 함께 사용하는 것이 효과적이에요.
  • 어떤 함수의 단위 테스트는 그 함수 바로 뒤에 나타나야 해요.

문서화된 단위 테스트 (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); */

더 알아보기