구글 C++ 테스트 프레임워크 고급 주제
updated Dec 6, 2010
원문: http://code.google.com/p/googletest/wiki/AdvancedGuide
입문서를 읽고 구글 테스트를 사용해 테스트를 작성하는 방법을 배웠으므로 몇 가지 새 기술을 배울 때다. 이 문서에서는 더 많은 단정문과 더불어 복잡한 실패 메시지를 만드는 법, 심각한 실패를 전파하며, 테스트 픽스처를 재사용하고 속력을 더 높이며, 테스트에 다양한 플래그를 사용하는 방법 등을 다룬다.
더 많은 단정문
여기서는 자주 사용하지는 않지만 중요한 단정문을 다룬다.
명시적인 성공과 실패
다음 세 가지 단정문은 실제로 값이나 표현식을 테스트하지 않는다. 대신 성공이나 실패를 직접 반환한다. 실제로 테스트를 실행하는 매크로와 마찬가지로 사용자 정의 실패 메시지를 스트림으로 전달할 수 있다.
SUCCEED(); |
성공을 반환한다. 전체 테스트를 성공으로 만드는 게 아니다. 테스트는 실행 중 실패한 단정문이 없을 때만 성공으로 간주한다.
참고: SUCCEED()는 순수하게 문서에만 있는 내용이며 현재는 사용자가 아무런 결과도 볼 수 없다. 하지만 SUCCEED() 메시지를 구글 테스트에 추가할 예정이다.
FAIL(); | ADD_FAILURE(); |
FAIL*_은 심각한 실패를 반환하지만 ADD_FAILURE*는 심각하지 않은 실패를 반환한다. 이는 테스트가 성공 또는 실패인지 결정하는 흐름 제어를 할 때 부울 표현식을 사용하는 것보다 유용하다. 예를 들어 다음과 같이 할 수 있다.
1 2 3 4 5 6 |
switch(expression) { case 1: ... some checks ... case 2: ... some other checks ... default: FAIL() << "We shouldn't get here."; } |
사용 가능: 리눅스, 윈도, 맥
예외 단정문
다음은 코드에서 예외가 발생하는지 아닌지 검증한다.
심각한 실패 단정문 | 심각하지 않은 실패 단정문 | 검증 내용 |
ASSERT_THROW(문장, 예외타입); | EXPECT_THROW(문장, 예외타입); | 문장에서 해당 타입의 예외가 발생한다. |
ASSERT_ANY_THROW(문장); | EXPECT_ANY_THROW(문장); | 문장에서 타입 관계없이 예외가 발생한다. |
ASSERT_NO_THROW(문장); | EXPECT_NO_THROW(문장); | 문장에서 아무런 예외도 발생하지 않는다. |
예제를 보자.
1 2 3 4 5 6 |
ASSERT_THROW(Foo(5), bar_exception); EXPECT_NO_THROW({ int n = 5; Bar(&n); }); |
사용 가능: 리눅스, 윈도, 맥. 1.1.0판부터.
더 나은 오류 메시지를 위한 술어함수 단정문
구글 테스트에는 많은 단정문 집합이 있지만 사용자가 실행하려는 모든 시나리오에 대응하는 건 불가능할 뿐만 아니라 좋은 생각도 아니기에 이는 완전하지 않다. 그러므로 때로는, 더 나은 매크로가 없기에 사용자는 EXPECT_TRUE()를 사용해 복잡한 표현식을 확인해야 한다. 이는 표현식에서 각 부분에 대한 값을 알 수 없어 무엇이 잘못됐는지 이해하기 어렵다. 차선책으로 일부 사용자는 실패 메시지를 만들어 EXPECT_TRUE()에 전달하지만 이는 평가 비용도 많이 들고 특히 표현식에 부가 효과가 있을 때는 다루기 어렵다.
구글 테스트에는 이런 문제를 해결하기 위한 세 가지 방법이 있다.
부울 함수 사용하기
bool 또는 암시적으로 bool로 변환할 수 있는 타입을 반환하는 함수나 함수자(functor)가 이미 있으면 술어함수 단정문에 사용해 함수 인자를 자유롭게 지정할 수 있다.
심각한 실패 단정문 | 심각하지 않은 실패 단정문 | 검증 내용 |
ASSERT_PRED1(pred1, val1); | EXPECT_PRED1(pred1, val1); | pred1(val1)은 참을 반환한다. |
ASSERT_PRED2(pred2, val1, val2); | EXPECT_PRED2(pred2, val1, val2); | pred2(val1, val2)는 참을 반환한다. |
…… | …… | …… |
위에서 predn은 n항 술어함수 또는 함수자이며 val1, val2, …, 그리고 valn은 인자이다. 술어함수에 해당 인자를 적용했을 때 참을 반환하면 단정문은 성공, 그렇지 않으면 실패이다. 단정문이 실패일 때는 각 인자값을 출력한다. 어느 경우든 인자는 정확히 한 번만 평가한다.
다음은 예제이다.
1 2 3 4 5 |
// Returns true if m and n have no common divisors except 1. bool MutuallyPrime(int m, int n) { ... } const int a = 3; const int b = 4; const int c = 10; |
EXPECT_PRED2(MuuallyPrime, a, b);는 성공이지만 EXPECT_PRED2(MutuallyPrime, b, c);는 실패이므로 다음 메시지를 출력한다.
1 2 3 |
MutuallyPrime(b, c) is false, where b is 4 c is 10 |
참고:
- ASSERT_PRED* 또는 EXPECT_PRED*를 사용할 때 ‘no matching function to call’ 컴파일러 오류가 발생하면 FAQ를 참고해 해결한다.
- 현재는 항 수가 5개 이하인 술어함수 단정문만 쓸 수 있으며 더 많이 필요하면 알려달라.
사용 가능: 리눅스, 윈도, 맥
AssertionResult를 반환하는 함수 사용하기
EXPECT_PRED*()와 그 부류는 간편해 재빨리 처리할 수 있지만 문법은 그리 만족스럽지 않다. 항 수에 따라 다른 매크로를 사용해야 하고 C++가 아닌 Lisp처럼 보이기 때문이다. 이는 ::testing::AssertionResult 클래스로 해결할 수 있다.
AssertionResult 객체는 단정문 결과[1]를 나타내는데, 다음 팩토리 함수 중 하나를 사용해 만들 수 있다.
1 2 3 4 5 6 7 8 9 10 11 |
namespace testing { // Returns an AssertionResult object to indicate that an assertion has // succeeded. AssertionResult AssertionSuccess(); // Returns an AssertionResult object to indicate that an assertion has // failed. AssertionResult AssertionFailure(); } |
그런 다음 << 연산자로 메시지를 AssertionResult 객체에 전달할 수 있다.
bool 대신 AssertionResult를 반환하는 술어함수를 만들어 EXPECT_TRUE()와 같은 부울 단정문에 더 이해하기 쉬운 메시지를 추가할 수도 있는데 다음은 그 예이다. IsEven()을
1 2 3 |
bool IsEven(int n) { return (n % 2) == 0; } |
대신
1 2 3 4 5 6 |
::testing::AssertionResult IsEven(int n) { if ((n % 2) == 0) return ::testing::AssertionSuccess(); else return ::testing::AssertionFailure() << n << " is odd"; } |
로 정의하면 EXPECT_TRUE(IsEven(Fib(4))) 단정문이 실패했을 때
1 2 3 |
Value of: IsEven(Fib(4)) Actual: false Expected: true |
처럼 불명확한 내용 대신
1 2 3 |
Value of: IsEven(Fib(4)) Actual: false (3 is odd) Expected: true |
으로 출력한다.
EXPECT_FALSE와 ASSERT_FALSE에서도 더 많은 정보가 필요하고 성공일 때 술어함수가 좀 느려져도 괜찮다면 성공 메시지를 추가할 수도 있다.
1 2 3 4 5 6 |
::testing::AssertionResult IsEven(int n) { if ((n % 2) == 0) return ::testing::AssertionSuccess() << n << " is even"; else return ::testing::AssertionFailure() << n << " is odd"; } |
이 때 EXPECT_FALSE(IsEven(Fib(6))) 문은 다음을 출력할 것이다.
1 2 3 |
Value of: IsEven(Fib(6)) Actual: true (8 is even) Expected: false |
사용 가능: 리눅스, 윈도, 맥. 1.4.1판부터.
술어함수 형식자(formatter) 사용하기
(ASSERT|EXPECT)_PRED*와 (ASSERT|EXPECT)_(TRUE|FALSE)에서 제공하는 기본 메시지가 만족스럽지 않거나 술어함수에 전달할 인자가 ostream에 스트림으로 전달할 수 없는 것이라면 술어함수 형식자 단정문을 사용해 메시지 형식을 완전히 원하는 대로 정의할 수 있다.
심각한 실패 단정문 | 심각하지 않은 실패 단정문 | 검증 내용 |
ASSERT_PRED_FORMAT1(pred_format1, val1); | EXPECT_PRED_FORMAT1(pred_format1, val1); | pred_format1(val1)은 성공이다. |
ASSERT_PRED_FORMAT2(pred_format2, val1, val2); | EXPECT_PRED_FORMAT2(pred_format2, val1, val2); | pred_format2(val1, val2)는 성공이다. |
…… | …… | …… |
이 것과 이전 두 그룹과 차이는 술어함수 대신 (ASSERT|EXPECT)_PRED_FORMAT*에서 함수 또는 함수자 시그니처(signature)로 술어함수 형식자(pred_formatn)를 취한다는 점이다.
1 |
::testing::AssertionResult PredicateFormattern(const char* expr1, const char* expr2, … const char* exprn, T1 val1, T2 val2, … Tn valn); |
에서 val1, val2, …, valn은 술어함수 인자값이고 expr1, expr2, …, exprn은 소스 코드에 있는 것과 일치하는 표현식이다. 타입 T1, T2, …, Tn은 값 또는 참조 타입일 수 있다. 예를 들어 인자 타입이 Foo라면 Foo 또는 const Foo&로 선전할 수 있으며 어느 것이든 괜찮다.
술어함수 형식자는 ::testing::AssertionResult 객체를 반환해 단정문이 성공인지 아닌지를 나타낸다. 이 객체를 만드는 유일한 방법은 팩토리 함수 중 하나를 호출하는 것이다.
EXPECT_PRED2()를 사용해 이전 예제에서 실패 메시지를 더 개선해 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Returns the smallest prime common divisor of m and n, // or 1 when m and n are mutually prime. int SmallestPrimeCommonDivisor(int m, int n) { ... } // A predicate-formatter for asserting that two integers are mutually prime. ::testing::AssertionResult AssertMutuallyPrime(const char* m_expr, const char* n_expr, int m, int n) { if (MutuallyPrime(m, n)) return ::testing::AssertionSuccess(); return ::testing::AssertionFailure() << m_expr << " and " << n_expr << " (" << m << " and " << n << ") are not mutually prime, " << "as they have a common divisor " << SmallestPrimeCommonDivisor(m, n); } |
술어함수 형식자를 다음처럼 사용할 수 있으며
1 |
EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c); |
출력 메시지는 다음과 같다.
1 |
b and c (4 and 10) are not mutually prime, as they have a common divisor 2. |
이미 알고 있을 수도 있겠지만 앞서 소개한 많은 단정문은 (EXPECT|ASSERT)_PRED_FORMAT*의 특별한 경우이며, 사실 그 단정문은 (EXPECT|ASSERT)_PRED_FORMAT*을 사용해 정의한다.
사용 가능: 리눅스, 윈도, 맥
부동소수점 비교
부동소수점을 비교하는 건 약간 기술이 필요하다. 반올림 오류 때문에 두 부동소수점이 정확히 일치한다는 건 불가능해 보인다. 그러므로 ASSERT_EQ와 같은 일반적인 비교는 할 수 없다. 게다가 부동소수점은 값 범위가 크므로 유효한 오차 한계(error bound)를 하나로 고정할 수 없다. 차라리, 0에 가까운 값은 정밀도(precision)가 떨어지므로 제외하고 고정된 상대 오차 한계(fixed relative error bound)로 비교하는 게 낫다.
일반적으로 부동소수점을 적절히 비교하려면 사용자는 오차 한계를 조심스럽게 선택해야 한다. 신경 쓰고 싶지 않으면 ULP(Units in the Last Place)[2]로 비교하는 게 기본적으로 좋으며 구글 테스트에서도 이를 위한 단정문이 있다. ULP에 대한 상세 내용은 꽤 길므로 더 배우고 싶으면 부동 소수점 비교에 대한 이 글을 참고한다.
부동소수점 매크로
심각한 실패 단정문 | 심각하지 않은 실패 단정문 | 검증 내용 |
ASSERT_FLOAT_EQ(기대값, 실제값); | EXPECT_FLOAT_EQ(기대값, 실제값); | 두 float 값이 거의 같다. |
ASSERT_DOUBLE_EQ(기대값, 실제값); | EXPECT_DOUBLE_EQ(기대값, 실제값); | 두 double 값이 거의 같다. |
‘거의 같다’는 말은 두 값이 서로에 대해 4 ULP 이내라는 것을 뜻한다.
다음 단정문에서는 허용 오차 한계(acceptable error bound)를 선택할 수 있다.
심각한 실패 단정문 | 심각하지 않은 실패 단정문 | 검증 내용 |
ASSERT_NEAR(val1, val2, 절대오차); | EXPECT_NEAR(val1, val2, 절대오차); | val1과 val2 사이 차이는 지정한 절대 오차를 넘지 않는다. |
사용 가능: 리눅스, 윈도, 맥
부동소수점 술어 형식 함수(Predicate-Format Function)
일부 부동소수점 연산은 유용하지만 자주 사용하지는 않으므로, 새 매크로가 폭발적으로 늘어나지 않도록 EXPECT_PRED_FORMAT2 등과 같이 술어 단정문 매크로로 사용할 수 있는 술어 형식 함수를 제공한다.
1 2 |
EXPECT_PRED_FORMAT2(::testing::FloatLE, val1, val2); EXPECT_PRED_FORMAT2(::testing::DoubleLE, val1, val2); |
val1은 val2보다 작거나 거의 같아야 한다. EXPECT_PRED_FORMAT2는 ASSERT_PRED_FORMAT2로 바꿔 쓸 수 있다.
사용 가능: 리눅스, 윈도, 맥
윈도 HRESULT 단정문
다음은 HRESULT가 성공인지 실패인지 테스트하는 단정문이다.
심각한 실패 단정문 | 심각하지 않은 실패 단정문 | 검증 내용 |
ASSERT_HRESULT_SUCCEEDED(표현식); | EXPECT_HRESULT_SUCCEEDED(표현식); | 표현식은 성공인 HRESULT이다. |
ASSERT_HRESULT_FAILED(표현식); | EXPECT_HRESULT_FAILED(표현식); | 표현식은 실패한 HRESULT이다. |
표현식에서 반환한 HRESULT 코드에 연관된 메시지를 사람이 읽을 수 있는 오류 메시지로 출력한다.
이 단정문은 다음처럼 사용할 수 있다.
1 2 3 4 |
CComPtr shell; ASSERT_HRESULT_SUCCEEDED(shell.CoCreateInstance(L"Shell.Application")); CComVariant empty; ASSERT_HRESULT_SUCCEEDED(shell->ShellExecute(CComBSTR(url), empty, empty, empty, empty)); |
사용 가능: 윈도
타입 단정문
타입 T1과 T2가 같은지 확인하기 위해 다음 함수를 호출할 수 있다.
1 |
::testing::StaticAssertTypeEq(); |
단정문을 만족하면 이 함수는 아무 것도 하지 않는다. 타입이 다르면 이 함수 호출에서 컴파일 실패하며 컴파일러 오류 메시지에서는 (컴파일러에 따라 다르겠지만) T1과 T2 실제 값을 보여 줄 것이다. 이는 주로 템플릿 코드 안에서 유용하다.
단서: 클래스 템플릿의 멤버 함수나 함수 템플릿 안에서 사용할 경우 StaticAssertTypeEq<T1, T2>()는 해당 함수를 인스턴스화 할 경우에만 효과가 있다. 예를 들어,
1 2 3 4 |
template class Foo { public: void Bar() { ::testing::StaticAssertTypeEq(); } }; |
와 같을 때
1 |
void Test1() { Foo foo; } |
이 코드에서는 결코 Foo<bool>::Bar()의 인스턴스를 만들지 않으므로 컴파일러 오류가 발생하지 않는다. 대신
1 |
void Test2() { Foo foo; foo.Bar(); } |
에서는 컴파일러 오류가 발생한다.
사용 가능: 리눅스, 윈도, 맥. 1.3.0판부터.
단정문 위치
단정문은 모든 C++ 함수에서 사용할 수 있다. 특히 테스트 픽스처 클래스의 메소드이지 않아도 된다. 한 가지 제약은, 심각한 실패를 반환하는 단정문(FAIL*과 ASSERT_*)은 값을 반환하지 않는(void-returning) 함수에서만 사용할 수 있다. 이는 구글 테스트에서 예외를 사용하지 않기 때문이다. 만약 값을 반환하는 함수에 그런 단정문을 사용하면 ‘error: void value not ignored as it ought to be’와 같은 혼란스런 컴파일러 오류가 발생할 것이다.
값을 반환하는 함수에서 단정문을 사용하려 할 때 한 가지 선택은, 값을 반환하는 대신 입출력 매개변수를 사용하는 것이다. 예를 들면, T2 Foo(T1 x)을 void Foo(T1 x, T2* result)로 바꾼다. 이때는 이르게 함수를 빠져 나가더라도 *result 값이 적절하도록 해야 한다. 이제 함수에서 값을 반환하지 않으므로 모든 단정문을 사용할 수 있다.
함수 형식을 바꾸지 않는다면 선택에 여지가 없다. ADD_FAILURE*와 EXPECT_* 같은 심각하지 않은 실패를 반환하는 단정문만 사용해야 한다.
참고: 생성자와 소멸자는 C++ 언어 사양에 따라 값을 반환하지 않는 함수로 간주하지 않으므로 심각한 실패 단정문을 사용할 수 없으며 그렇게 하면 컴파일러 오류가 생긴다. 간단한 차선책은 생성자나 소멸자 본체 전체를 값을 반환하지 않는 private 메소드로 옮기는 것이다. 하지만 그렇게 하면 생성자에서 심각한 단정문 실패가 생겨도 현재 테스트를 종료하지 않는다. 단지 생성자만 빠져나오므로 객체는 일부분만 생성된 상태일 수 있다. 마찬가지로 심각한 단정문 실패가 소멸자에서 발생하면 객체의 일부분만 소멸된 상태일 수 있다. 이런 상황에서는 단정문을 조심스럽게 사용해야 한다!
종료 테스트(Death Tests)
많은 응용프로그램에서는 조건이 맞지 않을 때 응용프로그램 실패를 일으키는 단정문이 있다. 프로그램이 올바른 상태에 있는지 확인하는 무결성 검사(sanity check)는 어떤 프로그램 상태가 망가지면 가능한 가장 빨리 실패한다. 만약 단정문에서 잘못된 상태를 확인하면 프로그램은 잘못된 상태로 진행할 것이고 이는 메모리 손상(memory corruption), 보안에 구멍(security holes)이 생기는 등 상태는 더욱 악화될 수 있다. 그러므로 그런 단정문이 기대하는 대로 동작하는지 테스트하는 것은 매우 중요하다.
이런 선행 조건 검사는 프로세스를 종료시킬 수도 있으므로 그런 테스트를 여기서는 종료 테스트(death test)라고 한다. 더 일반적으로는 프로그램이 기대한 방식으로 종료하는지 검사하는 테스트 역시 종료 테스트이다.
테스트 코드에서 EXPECT_*()/ASSERT_*() 실패를 테스트하려면 실패 받기(catching failures)를 참고한다.
종료 테스트를 만드는 법
구글 테스트에서는 다음 매크로에서 종료 테스트를 지원한다.
심각한 실패 단정문 | 심각하지 않은 실패 단정문 | 검증 내용 |
ASSERT_DEATH(문장, regex); | EXPECT_DEATH(문장, regex); | 문장은 에러를 발생하며 충돌한다. |
ASSERT_DEATH_IF_SUPPORTED(문장, regex); | EXPECT_DEATH_IF_SUPPORTED(문장, regex); | 종료 테스트를 지원하면 문장이 에러를 발생하며 충돌하는지 검증하고, 그렇지 않으면 아무것도 하지 않는다. |
ASSERT_EXIT(문장, 술어함수, regex); | EXPECT_EXIT(문장, 술어함수, regex); | 문장은 에러를 발생하며 종료하며, 종료 코드는 술어함수 결과와 일치한다. |
문장은 프로세스가 종료할 것으로 예상하는 내용이며, 술어함수는 평가 결과가 종료 상태를 나타내는 정수인 함수 또는 함수 객체이고, regex는 문장에서 표준 오류 출력(stderr output)으로 보내는 내용과 일치해야 하는 정규 표현식이다. 문장은 표현식이 아니어도 복합문을 포함해 유효한 문장이면 된다.
흔히 ASSERT류는 현재 테스트 함수를 종료하는 반면 EXPECT류는 그렇지 않다.
참고: 여기서 ‘충돌’은 프로세스가 종료할 때 상태 코드(exit status code)가 0이 아닌 것을 뜻한다. 두 가지 경우가 있을 수 있는데 프로세스에서 0이 아닌 값으로 exit()나 _exit()를 호출하거나 프로세스가 어떤 신호를 받아 종료할 때이다.
즉 프로세스를 종료하는 문장이 있을 때 종료 코드가 0이면 EXPECT_DEATH로 충돌을 확인할 수 없으므로, 이럴 때는 EXPECT_EXIT를 사용하거나 종료 코드를 더 세밀히 제한해야 한다.
술어함수는 int를 인자로 받고 bool을 반환해야 한다. 종료 테스트는 술어함수가 참을 반환할 때만 성공이다. 구글 테스트에서는 가장 일반적인 경우를 다루는 술어함수 몇 가지를 정의한다.
1 |
::testing::ExitedWithCode(exit_code) |
이 표현식은 프로그램이 해당 종료 코드로 마치면 참이다.
1 |
::testing::KilledBySignal(signal_number) // Not available on Windows. |
이 표현식은 프로그램이 해당 신호를 받아 종료하면 참이다.
*_DEATH 매크로는 술어 함수를 사용해 프로세스 종료 코드가 0이 아닌 것을 검증하는 *_EXIT를 편리하게 쓸 수 있도록 한 것이다.
종료 테스트는 다음 세 가지에만 관계 있다는 것에 주의한다.
- 문장이 프로세스를 중지 또는 종료하는가?
- ASSERT_EXIT와 EXPECT_EXIT인 경우 종료 상태가 술어함수를 만족하는가? 또는 ASSERT_DEATH와 EXPECT_DEATH인 경우 종료 상태가 0이 아닌가?
- 표준 오류 출력이 regex와 일치하는가?
특히 문장에서 ASSERT_* 또는 EXPECT_* 실패가 발생하면 종료 테스트 실패는 발생하지 않으므로 구글 테스트 단정문은 프로세스를 중지하지 않는다.
종료 테스트를 만들려면 테스트 함수에 위 매크로 중 하나를 사용하면 된다. 예를 들어
1 2 3 4 5 6 7 8 9 10 |
TEST(My*DeathTest*, Foo) { // This death test uses a compound statement. ASSERT_DEATH({ int n = 5; Foo(&n); }, "Error on line .* of Foo()"); } TEST(MyDeathTest, NormalExit) { EXPECT_EXIT(NormalExit(), ::testing::ExitedWithCode(0), "Success"); } TEST(MyDeathTest, KillMyself) { EXPECT_EXIT(KillMyself(), ::testing::KilledBySignal(SIGKILL), "Sending myself unblockable signal"); } |
이 내용은
- Foo(5)를 호출하면 지정한 에러 메시지를 출력하며 프로세스가 종료하는지
- NormalExit()를 호출하면 프로세스에서 표준 오류 출력으로 ‘Success’를 출력하고 종료 코드는 0으로 종료하는지
- KillMyself()를 호출하면 SIGKILL 신호를 보내 프로세스를 종료하는지
를 검증한다.
테스트 함수 본체에는 필요하면 다른 단정문이나 문장도 있을 수 있다.
중요: 테스트가 아니라 테스트 케이스 이름을 지을 때 종료 테스트를 포함하면 위 예제처럼 관례에 따라 *DeathTest로 하길 강력히 권한다. 다음에 나올 종료 테스트와 스레드 부분에서 그 이유를 설명하겠다.
일반 테스트와 종료 테스트에서 테스트 픽스처 클래스를 공유한다면 이름 때문에 코드를 중복하지 않도록 typedef를 사용해 픽스처 클래스에 별명을 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 |
class FooTest : public ::testing::Test { ... }; typedef FooTest FooDeathTest; TEST_F(FooTest, DoesThis) { // normal test } TEST_F(FooDeathTest, DoesThat) { // death test } |
사용 가능: 리눅스, 윈도(MSVC 8.0 이상 필요), Cygwin, 맥. 뒤에 셋은 1.3.0판부터 지원. (ASSERT|EXPECT)_DEATH_IF_SUPPORTED는 1.4.0판부터 지원.
정규 표현식 문법
(리눅스, Cygwin, 맥 등) POSIX 시스템에서 구글 테스트는 종료 테스트에 POSIX 확장 정규 표현식(POSIX extended regular expression) 문법을 사용한다. 이에 대해 배우고 싶으면 이 글을 읽어 보도록 한다.
윈도에서 구글 테스트는 단순한 자체 정규 표현식 구현을 사용한다. 이는 POSIX 확장 정규 표현식에 있는 많은 기능이 빠져있다. 예를 들면 선택(‘x|y’), 그룹(‘(xy)’), 범위(‘[xy]’) 그리고 반복 횟수(‘x{5,7}’) 등이다. 아래는 지원하는 내용이다(A는 마침표(.)나 이스케이프 문자 같은 문자 상수(literal character)를, x와 y는 정규 표현식을 나타낸다).
c | 모든 문자 상수 c와 일치한다. |
\\d | 모든 십진수와 일치한다. |
\\D | 십진수가 아닌 모든 문자와 일치한다. |
\\f | \f와 일치한다. |
\\n | \n과 일치한다. |
\\r | \r과 일치한다. |
\\s | \n을 포함해 모든 ASCII 공백 문자와 일치한다. |
\\S | 공백 문자가 아닌 모든 문자와 일치한다. |
\\t | \t와 일치한다. |
\\v | \v와 일치한다. |
\\w | _, 십진수, 모든 문자와 일치한다. |
\\W | \\w와 일치하지 않는 모든 문자와 일치한다. |
\\c | 문자가 구두점인 문자 상수 c와 일치한다. |
. | \n을 제외한 문자 하나와 일치한다. |
A? | A 개수가 0 또는 1인 것과 일치한다. |
A* | A 개수가 0 또는 다수인 것과 일치한다. |
A+ | A 개수가 1 또는 다수인 것과 일치한다. |
^ | 각 줄의 시작이 아닌 문자열의 시작과 일치한다. |
$ | 각 줄의 끝이 아닌 문자열의 끝과 일치한다. |
xy | x 다음에 y가 오는 것과 일치한다. |
사용하는 시스템에서 어떤 것을 사용할 수 있는지 확인할 수 있도록 구글 테스트에서는 매크로를 정의한다. POSIX 확장 정규 표현식을 사용할 수 있을 때는 GTEST_USES_POSIX_RE=1이고 단순 형식을 사용할 때는 GTEST_USES_SIMPLE_RE=1이다. 두 경우 모두에서 종료 테스트를 하려면 이 매크로에 #if를 사용하거나 제한된 문법만 사용해야 한다.
동작 방법
ASSERT_EXIT()에서는 새 프로세스를 만들고 그 프로세스 안에서 종료 테스트를 실행한다. 정확히 어떤 일이 벌어지는지는 플랫폼과 ::testing::GTEST_FLAG(death_test_style)[3]에 따라 다르다.
- POSIX 시스템에서는 fork()(또는 리눅스에서는 clone())를 사용해 자식 프로세스를 만든 후
- 그 변수 값이 ‘fast’이면 종료 테스트 문장을 즉시 실행한다.
- 그 변수 값이 ‘threadsafe’이면 자식 프로세스에서는 마치 처음 호출된 것처럼 단위 테스트 실행 파일을 다시 실행하는데, 플래그를 추가해 단일 종료 테스트가 되도록 한다.
- 윈도에서는 CreateProcess() API로 자식 프로세스를 만들고 POSIX에서 threadsafe와 매우 비슷하게 단일 종료 테스트가 되도록 단위 테스트 실행 파일을 새로 실행한다.
이 변수에 대해 다른 값을 사용하면 종료 테스트는 실패한다. 현재 이 플래그의 기본값은 ‘fast’이지만 앞으로 바꿀 예정이다. 그러므로 테스트할 때는 이에 의존하면 안 된다.
어느 쪽이든 부모 프로세스는 자식 프로세스가 종료하기를 기다리며 다음을 확인한다.
- 자식 프로세스 종료 상태가 술어 함수를 만족하고
- 자식 프로세스에서 표준 오류 출력으로 내보낸 내용이 정규 표현식과 일치한다.
종료하지 않고 종료 테스트 문 실행을 마치더라도 자식 프로세스는 종료하지만 단정문은 실패한다.
종료 테스트와 스레드
종료 테스트 형식이 두 가지인 이유는 스레드 안전성 때문이다. 스레드 생성에 대해 잘 알려진 문제 때문에 종료 테스트는 단일 스레드 컨텍스트에서 실행해야 한다. 하지만 때때로 그런 환경을 만들 수 없다. 예를 들어 정적 초기화 모듈에서는 주 스레드에서 실행하기 전에 스레드를 먼저 실행할 수도 있다[4]. 스레드는 일단 만들고 나면 정리하는 건 어렵거나 불가능하다.
구글 테스트에서는 스레드 문제를 알리기 위한 세 가지 기능이 있다.
- 종료 테스트를 해야 할 시점에 여러 스레드를 실행하고 있으면 경고를 보낸다.
- 이름이 ‘DeathTest’로 끝나는 테스트 케이스는 다른 테스트보다 먼저 실행한다.
- 부모 프로세스에 스레드가 여럿 있을 때 fork()는 자식 프로세스를 멈추므로 리눅스에서는 fork() 대신 clone()을 사용해 (clone()은 Cygwin과 맥에서 사용하지 못한다) 자식 프로세스를 만든다.
종료 테스트 문에서 스레드를 만드는 것은 아무런 문제 없다. 스레드는 별도 프로세스에서 실행하며 부모에 영향을 줄 수 없다.
종료 테스트 형식
‘threadsafe’ 종료 테스트 형식은 다중 스레드 환경에서 테스트할 때 생길 수 있는 위험을 줄이기 위한 것이다. 대신 스레드 안전성을 높이기 위해 테스트 실행 시간은 (극적으로) 증가한다. 특별히 문제가 없으면 기본인 ‘fast’ 형식을 사용하길 권한다.
프로그램에서 특정 종료 테스트 형식을 지정할 수 있다.
1 |
::testing::FLAGS_gtest_death_test_style = "threadsafe"; |
main()에 이 내용을 넣어 실행 파일에 있는 모든 테스트에 대해 지정하거나 개별 테스트에 넣어 지정할 수도 있다. 각 테스트를 실행하기 전에 플래그를 저장하고 마친 후 복원한다는 것을 기억할 것이다. 그러므로 직접 그런 일을 할 필요는 없다. 다음은 그 예이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
TEST(MyDeathTest, TestOne) { ::testing::FLAGS_gtest_death_test_style = "threadsafe"; // This test is run in the "threadsafe" style: ASSERT_DEATH(ThisShouldDie(), ""); } TEST(MyDeathTest, TestTwo) { // This test is run in the "fast" style: ASSERT_DEATH(ThisShouldDie(), ""); } int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::FLAGS_gtest_death_test_style = "fast"; return RUN_ALL_TESTS(); } |
단서
ASSERT_EXIT()의 인자 중 문장 부분은 유효한 C++ 문장이면 되나 현재 함수에서 빠져 나가지 않아야 한다. 즉 문장에 return 또는 ASSERT_TRUE() 등과 같이 빠져 나갈 수 있는 매크로를 포함하지 않아야 한다. 충돌하기 전에 문장에서 빠져 나가면 구글 테스트에서는 오류 메시지를 출력하고 테스트는 실패한다.
문장은 자식 프로세스에서 실행하므로 변수 변경, 메모리 해제 등과 같이 메모리에서 발생하는 모든 부수 효과는 부모 프로세스에서 알 수 없다. 특히 종료 테스트에서 메모리를 해제하면 부모 프로세스에서는 메모리가 해제된 것을 절대 모르므로 힙 검사는 실패한다. 이런 문제를 피하려면 다음과 같이 한다.
- 종료 테스트에서 메모리를 해제하지 않는다.
- 메모리를 해제는 부모 프로세스에서 한다.
- 또는 프로그램에서 힙 검사기를 사용하지 않는다.
구현 상의 문제로 같은 줄에 종료 테스트 단정문을 여러 개 놓을 수는 없다. 그렇게 하지 않으면 컴파일 중에 불명확한 오류 메시지가 발생하기 때문이다.
종료 테스트를 ‘threadsafe’ 형식으로 지정해 스레드 안전성을 높일 수 있지만 pthread_atfork(3)으로 등록했을 때 교착 상태와 같은 스레드 문제는 여전히 발생할 수 있다.
서브 루틴에서 단정문 사용하기
단정문에 추적 추가하기
여러 곳에서 테스트 서브 루틴을 호출할 때 그 안에 있는 단정문을 실패하면 어느 서브 루틴에서 실패했는지 말하기 어렵다. 추가 기록을 하거나 사용자 정의 실패 메시지를 사용해 이런 문제를 줄일 수 있지만 흔히 테스트 코드는 혼란스러워진다. 더 나은 방법은 SCOPED_TRACE 매크로를 사용하는 것이다.
SCOPED_TRACE(메시지); |
메시지는 std::outstream으로 전달할 수 있는 것이면 무엇이든 된다. 이 매크로는 현재 파일 이름, 줄 번호, 지정한 메시지를 모든 실패 메시지에 추가하는데, 제어가 해당 범위에 있을 때만 적용된다.
예를 들어
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
10: void Sub1(int n) { 11: EXPECT_EQ(1, Bar(n)); 12: EXPECT_EQ(2, Bar(n + 1)); 13: } 14: 15: TEST(FooTest, Bar) { 16: { 17: SCOPED_TRACE("A"); // This trace point will be included in 18: // every failure in this scope. 19: Sub1(1); 20: } 21: // Now it won't. 22: Sub1(9); 23: } |
이 결과 메시지는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 |
path/to/foo_test.cc:11: Failure Value of: Bar(n) Expected: 1 Actual: 2 Trace: path/to/foo_test.cc:17: A path/to/foo_test.cc:12: Failure Value of: Bar(n + 1) Expected: 2 Actual: 3 |
추적을 사용하지 않으면 각 실패에 대해 어느 Sub1() 호출인지 알기 어렵다(n 값을 알려고 Sub1()에 있는 각 단정문에 메시지를 추가할 수도 있지만 너무 장황하다).
다음은 SCOPED_TRACE를 사용할 때 유용한 팁 몇 가지이다.
- 적절한 메시지를 사용하면 SCOPED_TRACE를 호출하는 곳이 아니라 서브루틴 시작 부분에 사용해도 된다.
- 루프 안에서 서브루틴을 호출하면 얼마나 반복하다 실패했는지 알 수 있도록 SCOPED_TRACE 메시지에 루프 반복자 부분을 둔다.
- 때로는 추적 위치의 줄 번호만으로도 서브루틴에 대한 특정 호출을 확인하기엔 충분하다. 이 때는 SCOPED_TRACE에 메시지를 지정할 필요 없이 “”를 사용하면 된다.
- 바깥 범위에 SCOPE_TRACE가 있을 때 안쪽 범위에도 사용하면, 활성화 된 모든 추적 위치에 대한 메시지를 지나온 반대 순서로 실패 메시지에 추가한다.
- 추적 덤프 내용은 이맥스(emacs) 컴파일 버퍼에서 선택할 수 있는데 줄 번호를 선택하면 해당 소스 파일에서 그 위치로 간다.
사용 가능: 리눅스, 윈도, 맥
심각한 실패 전파하기
ASSERT_*와 FAIL_*을 사용할 때 흔히 하는 실수는 실패하면 전체 테스트가 아니라 현재 함수만 중지한다는 점을 이해하지 않는 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void Subroutine() { // Generates a fatal failure and aborts the current function. ASSERT_EQ(1, 2); // The following won't be executed. ... } TEST(FooTest, Bar) { Subroutine(); // The intended behavior is for the fatal failure // in Subroutine() to abort the entire test. // The actual behavior: the function goes on after Subroutine() returns. int* p = NULL; *p = 3; // Segfault! } |
구글 테스트에서는 예외를 사용하지 않으므로 전체를 중지하도록 구현하는 건 기술적으로 불가능하다. 그러므로 구글 테스트에서는 두 가지 해결책을 제공한다. (ASSERT|EXPECT)_NO_FATAL_FAILURE 단정문이나 HasFatalFailure() 함수를 사용할 수 있다. 자세한 내용은 아래에서 설명한다.
서브루틴에서 단정문 사용하기
위에서 본 것처럼 테스트에 있는 서브루틴에서 ASSERT_* 실패를 하더라도 해당 서브루틴 이후부터 계속 진행하므로 이를 바라지 않을 수 있다.
종종 심각한 실패를 예외처럼 전파하려 하므로 구글 테스트에서는 다음 매크로를 제공한다.
심각한 실패 단정문 | 심각하지 않은 실패 단정문 | 검증 내용 |
ASSERT_NO_FATAL_FAILURE(문장); | EXPECT_NO_FATAL_FAILURE(문장); | 문장은 현재 스레드에서 새로운 심각한 실패를 전혀 발생하지 않는다. |
단정문을 실행하는 스레드에서 실패할 때만 이런 타입의 단정문 결과를 결정하기 위해 확인한다. 문장에서 새 스레드를 만들면 그 스레드에서 실패는 무시한다.
예는 다음과 같다.
1 2 3 4 5 6 |
ASSERT_NO_FATAL_FAILURE(Foo()); int i; EXPECT_NO_FATAL_FAILURE({ i = Bar(); }); |
사용 가능: 리눅스, 윈도, 맥. 다중 스레드에서 단정문은 현재 지원하지 않는다.
현재 테스트에서 실패 확인하기
::testing::Test 클래스에 있는 HasFatalFailure()는 현재 테스트에 심각한 실패가 발생할 때 true을 반환한다. 이를 통해 서브루틴에서 심각한 실패를 잡아 빨리 함수에서 빠져 나올 수 있다.
1 2 3 4 5 |
class Test { public: ... static bool HasFatalFailure(); }; |
예외 발생과 유사한 동작을 하는 가장 일반적인 사용법은 다음과 같다.
1 2 3 4 5 6 7 8 |
TEST(FooTest, Bar) { Subroutine(); // Aborts if Subroutine() had a fatal failure. if (HasFatalFailure()) return; // The following won't be executed. ... } |
HasFatalFailure()를 TEST(), TEST_F() 또는 테스트 픽스처 바깥에서 사용하려면 다음처럼 ::testing::Test:: 접두어를 붙여야 한다.
1 2 |
if (::testing::Test::HasFatalFailure()) return; |
이와 비슷하게 HasNonfatalFailure()에서는 현재 테스트에 심각하지 않은 실패가 최소 하나라도 있으면 true를 반환하고, HasFailure()에서는 현재 테스트에 종류에 상관 없이 실패가 최소 하나라도 있으면 true를 반환한다.
사용 가능: 리눅스, 윈도, 맥. HasNonfatalFailure()와 HasFailure()는 1.4.0판부터 사용할 수 있다.
추가 정보 기록하기
테스트 코드에서 RecordProperty(“키”, 값)을 호출해 추가 정보를 기록할 수 있다. 여기서 값은 C 문자열이나 32비트 정수이다. 키를 지정하면 해당 키에 대해 기록한 마지막 값을 XML로 내 보낸다. 예를 들어 테스트가
1 2 3 4 |
TEST_F(WidgetUsageTest, MinAndMaxWidgets) { RecordProperty("MaximumWidgets", ComputeMaxUsage()); RecordProperty("MinimumWidgets", ComputeMinUsage()); } |
이면 XML 출력 결과는 다음과 같다.
1 2 3 |
... <testcase name="MinAndMaxWidgets" status="run" time="6" classname="WidgetUsageTest" MaximumWidgets="12" MinimumWidgets="9" /> ... |
참고:
- RecordProperty()는 테스트 클래스에서 정적 멤버이므로 TEST 본체와 테스트 픽스처 클래스 밖에서 사용하려면 ::testing::Test:: 접두어를 붙여야 한다.
- 키는 유효한 XML 속성 이름이어야 하며 구글 테스트에 이미 있는 것(name, status, time 그리고 classname 등)과 충돌하지 않아야 한다.
사용 가능: 리눅스, 윈도, 맥
같은 테스트 케이스에 있는 테스트에서 자원 공유하기
구글 테스트에서는 각 테스트에 대해 픽스처 객체를 만들어 테스트를 독립적이고 디버그 하기 쉽도록 한다. 하지만 때때로 테스트에서는 설정하는데 비용이 많이 드는 자원을 사용하기도 하며, 이런 경우 테스트 당 복사본을 하나씩 만드는 이 모델은 비용이 엄청나다.
테스트에서 자원에 변화를 주지 않으면 자원에 대한 복사본 하나만 공유해도 아무 문제없다. 그러므로 테스트마다 설정, 해제하는 것에 추가로 구글 테스트에서는 테스트 케이스별로 설정, 해제하는 것도 지원한다. 이를 사용하려면
- 테스트 픽스처 클래스(예를 들면 FooTest)에서 공유 자원을 담을 멤버 변수를 static으로 정의한다.
- 같은 테스트 픽스처 클래스에서 공유 자원을 설정하는데 사용할 static void SetUpTestCase() 함수(SetupTestCase처럼 소문자 u로 사용하지 않도록 주의한다!)와 해제하는데 사용할 static void TearDownTestCase() 함수를 정의한다.
그 뿐이다. 구글 테스트에서는 FooTest 테스트 케이스에 첫 번째 테스트를 실행하기 전(즉 첫 FooTest 객체를 생성하기 전)에 자동으로 SetUpTestCase()를 호출하고, 마지막 테스트를 실행한 후 (즉 FooTest 객체를 삭제한 후) TearDownTestCase()를 호출한다.
테스트 순서는 정해져 있지 않으므로 각 테스트는 선후 관계가 없어야 한다는 것을 기억한다. 또한 테스트에서는 공유 자원의 상태를 변경하지 않아야 하며, 만약 변경한다면 다음 테스트로 제어를 넘기기 전에 원래 상태로 복원해야 한다.
다음은 테스트 케이스마다 설정, 해제하는 예이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class FooTest : public ::testing::Test { protected: // Per-test-case set-up. // Called before the first test in this test case. // Can be omitted if not needed. static void SetUpTestCase() { shared_resource_ = new ...; } // Per-test-case tear-down. // Called after the last test in this test case. // Can be omitted if not needed. static void TearDownTestCase() { delete shared_resource_; shared_resource_ = NULL; } // You can define per-test set-up and tear-down logic as usual. virtual void SetUp() { ... } virtual void TearDown() { ... } // Some expensive resource shared by all tests. static T* shared_resource_; }; T* FooTest::shared_resource_ = NULL; TEST_F(FooTest, Test1) { ... you can refer to shared_resource here ... } TEST_F(FooTest, Test2) { ... you can refer to shared_resource here ... } |
사용 가능: 리눅스, 윈도, 맥
전역 설정과 해제
테스트 수준과 테스트 케이스 수준에서 자원을 설정, 해제할 수 있는 것처럼 테스트 프로그램 수준에서도 할 수 있다. 방법은 다음과 같다.
먼저 테스트 환경을 정의하기 위해 ::testing::Environment 클래스에서 파생한 하위 클래스를 만든다.
1 2 3 4 5 6 7 8 |
class Environment { public: virtual ~Environment() {} // Override this to define how to set up the environment. virtual void SetUp() {} // Override this to define how to tear down the environment. virtual void TearDown() {} }; |
그런 다음 ::testing::AddGlobalTestEnvironment() 함수를 호출해 구글 테스트에 환경 클래스 인스턴스를 등록한다.
1 |
Environment* AddGlobalTestEnvironment(Environment* env); |
이제 RUN_ALL_TEST()를 호출하면 환경 객체에서 SetUp() 메소드를 먼저 호출하고 심각한 실패가 없으면 테스트를 실행한 후 마지막으로 환경 객체에서 TearDown()을 호출한다.
환경 객체를 여럿 등록할 수도 있다. 이 때 SetUp()은 등록한 순서대로 호출하고 TearDown()은 역순으로 호출한다.
등록한 환경 객체는 구글 테스트에 소유권이 있으므로 직접 그 객체를 삭제하면 안 된다.
RUN_ALL_TEST()를 호출하기 전에 AddGlobalTestEnvironment()를 호출해야 하며, 만약 gtest_main을 사용한다면 main()에서 이 라이브러리를 실행하기 전에 이 함수를 호출해야 한다. 한 가지 방법은 다음처럼 전역 변수를 정의하는 것이다.
1 |
:testing::Environment* const foo_env = ::testing::AddGlobalTestEnvironment(new FooEnvironment); |
하지만 전역 변수 초기화를 사용하면 코드 읽기가 어려워지고 다른 번역 단위에서 서로 의존 관계가 있는 여러 환경을 등록하면 문제가 생길 수 있으므로(컴파일러에서는 다른 번역 단위에 있는 전역 변수 사이에 초기화 순서를 보장하지 않는다는 것을 기억한다), main()을 직접 만들고 그 안에서 AddGlobalTestEnvironment()를 호출하기를 강력히 권한다.
사용 가능: 리눅스, 윈도, 맥
값 매개 변수 테스트(Value Parameterized Test)
값 매개변수 테스트를 사용하면 같은 테스트에 대한 복사본을 여럿 만들지 않아도 매개변수를 달리해 코드를 테스트할 수 있다.
코드에 대한 테스트를 만든 후, 부울 명령 행 플래그가 있으면 코드에 영향이 있다는 것을 알았다고 하자.
1 2 3 |
TEST(MyCodeTest, TestFoo) { // A code to test foo(). } |
이럴 때는 흔히 상황에 맞춰 부울 매개변수를 사용하는 함수를 만들어 테스트 코드를 넣는다. 이 함수에서는 플래그를 설정하고 테스트 코드를 실행한다.
1 2 3 4 5 6 7 8 9 |
void TestFooHelper(bool flag_value) { flag = flag_value; // A code to test foo(). } TEST(MyCodeTest, TestFooo) { TestFooHelper(false); TestFooHelper(true); } |
하지만 이 것에는 심각한 단점이 있다. 먼저 테스트에서 단정문이 실패하면 어떤 값 때문에 그렇게 됐는지 불명확해진다. EXPECT/ASSERT 문에 명확한 메시지를 전달할 수 있지만 모든 단정문에 대해 그렇게 해야 한다. 두 번째로 테스트마다 도우미 함수 같은 것을 추가해야 한다. 그런데 테스트가 열 개면 어떻게 될까? 스무 개, 백 개이면?
값 매개변수 테스트를 사용하면 테스트를 한 번만 작성하고 쉽게 인스턴스를 만든 다음 매개변수 값으로 임의의 수를 지정해 실행할 수 있다.
다음은 값 매개변수 테스트가 적절한 몇 가지 상황이다.
- OO 인터페이스를 다르게 구현한 것을 테스트할 때.
- 다양한 입력에 대해 테스트를 할 때(데이터 주도 테스트라고 한다). 이 기능은 남용하기 쉬우므로 적절히 사용하도록 연습하길 바란다!
값 매개변수 테스트를 만드는 법
값 매개변수 테스트를 만들려면 먼저 픽스처 클래스를 정의한다. 해당 클래스는 ::testing::TestWithParam<T>에서 파생해야 한다. 여기서 T는 매개변수 값의 타입이다. TestWithParam<T>는 ::testing::Test에서 파생했으며 T는 복사할 수 있는 타입이다. 만약 이 타입이 저수준 포인터라면 가리키는 값의 생명 주기를 직접 관리해야 한다.
1 2 3 4 5 |
class FooTest : public ::testing::TestWithParam<const char*> { // You can implement all the usual fixture class members here. // To access the test parameter, call GetParam() from class // TestWithParam<T>. }; |
그런 다음 TEST_P 매크로를 사용해 이 픽스처를 사용하는 테스트 패턴을 필요한 만큼 정의한다. _P 접미어는 ‘매개변수’ 또는 ‘패턴’을 나타내는데 맘에 드는 대로 생각하면 된다.
1 2 3 4 5 6 7 8 9 10 |
TEST_P(FooTest, DoesBlah) { // Inside a test, access the test parameter with the GetParam() method // of the TestWithParam<T> class: EXPECT_TRUE(foo.Blah(GetParam())); ... } TEST_P(FooTest, HasBlahBlah) { ... } |
마지막으로 INSTANTIATE_TEST_CASE_P를 사용해 필요한 매개변수 집합과 함께 테스트 케이스를 인스턴스화 한다. 구글 테스트에서는 테스트 매개변수를 만들어 내는 함수를 많이 정의하고 있는데, 이 함수에서는 호출한 매개변수 생성자의 결과를 반환한다. 다음은 testing 네임스페이스에 있는 모든 생성자에 대한 요약 내용이다.
Range(begin, end[, step[) |
{begin, begin+step, begin+step+step, …} 값을 만든다. 이 값에 end는 포함하지 않으며 step은 1이 기본값이다. |
Values(v1, v2, …, vN) |
{v1, v2, …, vN} 값을 만든다. |
ValuesIn(container) 와 ValuesIn(begin, end) |
C 배열, STL 컨테이너 또는 반복자 범위 [begin, end)에서 값을 만든다. |
Bool() |
{false, true}를 만든다. |
Combine(g1, g2, …, gN) |
N 생성자로 만들 수 있는 값의 모든 조합(수학에서 곱집합)을 만든다. 시스템에서 <tr1/tuple> 헤더를 지원할 때만 사용할 수 있다. 시스템에서는 지원하는데 구글 테스트에서 사용할 수 없으면 GTEST_HAS_TR1_TUPLE=1을 정의해 사용할 수 있다. 더 자세한 정보는 include/gtest/internal/gtest-port.h 파일에 있는 주석문을 확인한다. |
더 자세한 내용은 소스 코드에서 각 함수 정의에 있는 주석문을 확인한다.
다음 문장은 매개변수 값 ”meeny”, “miny”, “moe” 각각에 대해 FooTest 테스트 케이스에 있는 테스트를 인스턴스화 한다.
1 2 3 |
INSTANTIATE_TEST_CASE_P(InstantiationName, FooTest, ::testing::Values("meeny", "miny", "moe")); |
(인스턴스를 한 번 이상 만들 수 있으므로) 해당 패턴에 대한 여러 인스턴스를 구별하기 위해 INSTANTIATE_TEST_CASE_P에 대한 첫 번째 인자를 실제 테스트 케이스 이름에 접두어로 추가한다. 인스턴스는 다르지만 접두어는 같다는 점을 기억하자. 위 경우 인스턴스를 만들면 테스트 이름은 다음과 같다.
- “meeny”에 대해 InstantiationName/FooTest.DoesBlah/0
- “miny”에 대해 InstantiationName/FooTest.DoesBlah/1
- “moe”에 대해 InstantiationName/FooTest.DoesBlah/2
- “meeny”에 대해 InstantiationName/FooTest.HasBlahBlah/0
- “miny”에 대해 InstantiationName/FooTest.HasBlahBlah/1
- “moe”에 대해 InstantiationName/FooTest.HasBlahBlah/2
이 이름을 –gtest_filter에서 사용할 수 있다.
다음 문장은 FooTest에 있는 모든 테스트를 다시 인스턴스화 하며 각각에 대한 매개변수는 ”cat“과 “dog”이다.
1 2 3 |
const char* pets[] = {"cat", "dog"}; INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ::testing::ValuesIn(pets)); |
인스턴스로 만든 테스트 이름은 다음과 같다.
- “cat”에 대해 AnotherInstantiationName/FooTest.DoesBlah/0
- “dog”에 대해 AnotherInstantiationName/FooTest.DoesBlah/1
- “cat”에 대해 AnotherInstantiationName/FooTest.HasBlahBlah/0
- “dog”에 대해 AnotherInstantiationName/FooTest.HasBlahBlah/1
INSTANTIATE_TEST_CASE_P는 지정한 테스트 케이스에 있는 모든 테스트를 인스턴스화 한다는 점에 주의한다. 이는 테스트 정의가 INSTANTIATE_TEST_CASE_P 문장 앞 또는 뒤, 어디에 있든 관계 없다.
더 많은 예는 이 파일[5]을 살펴본다.
사용 가능: 리눅스, 윈도(MSVC 8.0 이상 필요), 맥. 1.2.0판부터.
값 매개변수 추상 테스트 만들기
위에서는 같은 소스 파일에서 FooTest를 정의하고 인스턴스를 만들었다. 때로는 값 매개변수 테스트를 라이브러리에 정의하고 나중에 이를 인스턴스화 하길 바랄 수도 있다. 이 패턴을 추상 테스트(abstract test)라 한다. 이에 대한 예를 들면, 인터페이스를 설계할 때 해당 인터페이스의 모든 구현이 통과해야 하는 추상 테스트 표준 집합(standard suite)을 만들 수 있다. 누군가 그 인터페이스를 구현할 때는 만들어 둔 표준 집합을 자유롭게 인스턴스화 해 인터페이스 적합성 테스트를 할 수 있다.
추상 테스트를 정의하려면 코드를 다음처럼 만들어야 한다.
- 매개변수 테스트 픽스처 클래스(예를 들면 FooTest)를 헤더 파일(예를 들면 foo_param_test.h)에 정의한다. 이는 추상 테스트를 선언하는 것으로 생각할 수 있다.
- foo_param_test.cc에 TEST_P를 정의하며 이 파일에서는 foo_param_test.h를 포함한다. 이는 추상 테스트를 구현하는 것으로 생각할 수 있다.
정의를 한 다음, foo_param_test.h를 포함하고 INSTANTIATE_TEST_CASE_P()를 호출하며 foo_param_test.cc를 링크해 인스턴스를 만들 수 있다. 소스 파일이 달라도 같은 추상 테스트 케이스를 여러 번 인스턴스화 할 수 있다.
타입 테스트(Typed Test)
같은 인터페이스에 대한 구현이 여럿일 때 모든 구현에서 어떤 공통 조건을 만족해야 한다고 하자. 또는 같은 ‘개념’을 나타내는 타입 몇 가지를 정의하고 이를 검증하려 한다. 두 경우 모두 타입은 다르지만 같은 테스트 논리를 사용하고 싶다.
테스트할 타입별로 TEST나 TEST_F를 만들 수도 있지만 (그런 다음 TEST에서 호출하는 함수 템플릿에 테스트 논리를 넣을 수도 있지만) 이는 장황하고 상황에 따라 바꾸기 어렵다. 테스트 m 개를 n 개 타입에 대해 해야 한다면 테스트를 m*n 개만큼 만들어야 한다.
타입 테스트를 사용하면 타입 목록에 대해 같은 테스트 논리를 반복할 수 있다. 타입 테스트를 만들 때 타입 목록을 알아야 하지만 테스트 논리는 한 번만 만들면 된다. 방법은 다음과 같다.
먼저 타입 매개변수를 사용하는 픽스처 클래스 템플릿을 정의한다. 이 템플릿은 ::testing::Test에서 파생해야 하는 걸 기억하자.
1 2 3 4 5 6 7 8 |
template <typename T> class FooTest : public ::testing::Test { public: ... typedef std::list<T> List; static T shared_; T value_; }; |
다음은 목록에 있는 각 타입에 대해 테스트 케이스를 반복할 수 있도록, 테스트 케이스와 타입 목록을 연결한다.
1 2 |
typedef ::testing::Types<char, int, unsigned int> MyTypes; TYPED_TEST_CASE(FooTest, MyTypes); |
typedef는 TYPED_TEST_CASE 매크로를 올바로 파싱하는데 필요하다. 사용하지 않으면 컴파일러에서는 타입 목록에 있는 각 쉼표를 새 매크로 인자가 시작하는 걸로 판단한다.
이제 TEST_F() 대신 TYPED_TEST()를 사용해 테스트 케이스에 대한 타입 테스트를 정의한다. 필요한 만큼 이 과정을 반복한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
TYPED_TEST(FooTest, DoesBlah) { // Inside a test, refer to the special name TypeParam to get the type // parameter. Since we are inside a derived class template, C++ requires // us to visit the members of FooTest via 'this'. TypeParam n = this->value_; // To visit static members of the fixture, add the 'TestFixture::' // prefix. n += TestFixture::shared_; // To refer to typedefs in the fixture, add the 'typename TestFixture::' // prefix. The 'typename' is required to satisfy the compiler. typename TestFixture::List values; values.push_back(n); ... } TYPED_TEST(FooTest, HasPropertyA) { ... } |
samples/sample6_unittest.cc에서 예제 전체를 볼 수 있다.
사용 가능: 리눅스, 윈도(MSVC 8.0 이상 필요), 맥. 1.1.0판부터.
타입 매개변수 테스트(Type-Parameterized Test)
타입 매개변수 테스트는 타입 목록을 먼저 알 필요가 없다는 점만 빼고 타입 테스트와 비슷하다. 대신 테스트 논리를 먼저 만들고 나중에 다른 타입 목록을 사용해 인스턴스를 만든다. 심지어 같은 프로그램 내에서 인스턴스를 한 번 이상 만들 수도 있다.
인터페이스나 개념을 설계하고 있다면 그 것을 구현한 것이 유효한지 검증하기 위한 타입 매개변수 테스트를 정의할 수 있다. 그러면 각 구현 내용 개발자는 요구 사항에 맞는지 확인하기 위해 비슷한 테스트를 다시 만들 필요 없이, 필요한 타입을 사용해 정의해 둔 테스트로 인스턴스를 만들면 된다. 다음은 그 예이다.
먼저 픽스처 클래스 템플릿을 정의한다. 여기에서는 타입 테스트에서 한 것처럼 정의했다.
1 2 3 4 |
template <typename T> class FooTest : public ::testing::Test { ... }; |
다음으로 타입 매개변수 테스트 케이스로 정의할 것을 선언한다.
1 |
TYPED_TEST_CASE_P(FooTest); |
_P 접미어는 ‘매개변수’ 또는 ‘패턴’을 나타내는데 맘에 드는 걸로 생각하면 된다.
그런 다음 TYPED_TEST_P()를 사용해 타입 매개변수 테스트를 정의한다. 필요한 만큼 이 과정을 반복한다.
1 2 3 4 5 6 7 |
TYPED_TEST_P(FooTest, DoesBlah) { // Inside a test, refer to TypeParam to get the type parameter. TypeParam n = 0; ... } TYPED_TEST_P(FooTest, HasPropertyA) { ... } |
지금부터는 약간 어렵다. 테스트 패턴을 인스턴스로 만들기 전에 REGISTER_TYPED_CASE_P 매크로를 사용해 모든 테스트를 등록해야 한다. 이 매크로에서 첫 인자는 테스트 케이스 이름이고 나머지는 이 테스트 케이스에 있는 테스트 이름이다.
1 2 |
REGISTER_TYPED_TEST_CASE_P(FooTest, DoesBlah, HasPropertyA); |
이제는 지정한 패턴을 원하는 타입을 사용해 자유롭게 인스턴스로 만들 수 있다. 즉 위 코드를 넣은 헤더 파일을 #include로 여러 C++ 소스 파일에 포함하면 여러 번 인스턴스로 만들 수 있다.
1 2 |
typedef ::testing::Types MyTypes; INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); |
각 패턴에 대한 인스턴스는 INSTANTIATE_TYPED_TEST_CASE_P 매크로에서 첫 번째 인자를 실제 테스트 케이스 이름에 접두어로 추가해 구별한다.
타입 목록에 타입이 하나인 특별한 경우에는 ::testing::Types<…>을 사용하지 않고 다음처럼 직접 쓸 수 있다.
1 |
INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, int); |
전체 예제는 samples/sample6_unittest.cc 파일을 참조한다.
사용 가능: 리눅스, 윈도(MSVC8.0 이상 필요), 맥. 1.1.0부터.
비공개 코드 테스트하기
소프트웨어 내부 구현을 바꾸더라도 그 변경 내용을 사용자가 알 수 없으면 테스트 역시 망가지지 않아야 한다. 그러므로 블랙 박스 테스트 원칙(black-box testing principle)에 따라 대부분은 공용 인터페이스를 통해 코드를 테스트하는 게 좋다.
여전히 내부 구현 내용을 테스트해야 한다면 그렇게 하지 않을 수 있는 더 좋은 설계가 없는지 먼저 고려한다. 정말로 비공개(private) 인터페이스 코드를 테스트해야 한다면 할 수 있으나 다음 두 가지를 고려해야 한다.
- 정적 함수(정적 멤버 함수와 다르다!) 또는 이름 없는 네임스페이스와
- private 또는 protected 클래스 멤버
정적 함수
정적 함수와 이름 없는 네임스페이스에 있는 정의와 선언은 모두 같은 번역 단위(translation unit) 안에서만 볼 수 있다[6]. *_test.cc 파일에서 테스트할 모든 .cc 파일을 #include로 포함해 테스트할 수 있다(.cc 파일을 #include로 포함하는 건 코드 재사용에 있어 그리 좋은 방법은 아니다. 제품 코드에는 이런 방법을 사용하지 않는 게 좋다).
더 좋은 방법은 비공개 코드를 foo::internal 네임스페이스로 옮기고 *-internal.h 파일에 비공개 선언을 두는 것이다. 여기서 foo는 프로젝트에서 일반적으로 사용하는 네임스페이스이다. 제품 .cc 파일과 테스트에서는 이 내부 헤더를 포함할 수 있으나 클라이언트에서는 그렇게 하지 못한다. 이 방법으로 내부 구현을 클라이언트에 보이지 않고도 모두 테스트할 수 있다.
private 클래스 멤버
private 클래스 멤버는 해당 클래스나 프렌드에서만 접근할 수 있다. private 클래스 멤버에 접근하기 위해 테스트 픽스처를 해당 클래스에 대한 프렌드로 선언하고 픽스처에서 접근자를 정의한다. 그렇게 하면 그 픽스처를 사용하는 테스트에서는 해당 픽스처 내 접근자를 통해 제품 클래스의 private 멤버에 접근할 수 있다. 단, 픽스처가 제품 클래스에 대한 프렌드일지라도 테스트는 자동으로 그 클래스에 대한 프렌드가 안 된다는 점에 주의한다. 기술적으로 테스트는 해당 픽스처에 대한 하위 클래스로 정의하기 때문이다.
private 멤버를 테스트하는 다른 방법은 구현 클래스로 그 내용을 옮기고 선언을 *-internal.h 파일에 하는 것이다. 클라이언트에서는 이 파일을 포함할 수 없지만 테스트에서는 할 수 있다. 이런 것을 Pimpl(Private Implementation) 관용어구라 한다.
또는 해당 클래스 본체에 다음 내용을 추가해 개별 테스트를 클래스 프렌드로 선언할 수 있다.
1 |
FRIEND_TEST(TestCaseName, TestName); |
다음은 그 예이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// foo.h #include <gtest/gtest_prod.h> // Defines FRIEND_TEST. class Foo { ... private: FRIEND_TEST(FooTest, BarReturnsZeroOnNull); int Bar(void* x); }; // foo_test.cc ... TEST(FooTest, BarReturnsZeroOnNull) { Foo foo; EXPECT_EQ(0, foo.Bar(NULL)); // Uses Foo's private member Bar(). } |
클래스를 네임스페이스 안에 정의할 때는 특히 주의해야 한다. 테스트 픽스처와 테스트를 클래스에 프렌드로 만들려면 같은 네임스페이스 안에 정의해야 한다. 예를 들어 테스트할 코드가 다음과 같다면
1 2 3 4 5 6 7 8 9 10 11 12 |
namespace my_namespace { class Foo { friend class FooTest; FRIEND_TEST(FooTest, Bar); FRIEND_TEST(FooTest, Baz); ... definition of the class Foo ... }; } // namespace my_namespace |
테스트 코드는 다음과 같아야 한다.
1 2 3 4 5 6 7 8 9 10 |
namespace my_namespace { class FooTest : public ::testing::Test { protected: ... }; TEST_F(FooTest, Bar) { ... } TEST_F(FooTest, Baz) { ... } } // namespace my_namespace |
실패 잡기
구글 테스트를 바탕으로 테스트 유틸리티를 만든다면 그 유틸리티를 테스트하고 싶을 것이다. 어떤 프레임워크로 테스트할까? 물론 구글 테스트이다.
문제는 테스트 유틸리티에서 실패 보고를 올바로 하는지 검증하는 것이다. 예외를 발생해 실패 보고를 하는 프레임워크에서는 그 예외를 잡아 확인할 수 있다. 하지만 구글 테스트에서는 예외를 사용하지 않는데 어떻게 기대한 실패가 발생했는지 테스트할 수 있을까?
<gtest/gtest-spi.h>에는 이를 위한 몇 가지가 있다. #include로 이 헤더를 포함하면
EXPECT_FATAL_FAILURE(문장, 부분문자열); |
를 사용해 문장에서 지정한 부분문자열을 포함하는 (ASSERT_* 등과 같이) 심각한 실패 메시지를 만드는지 확인할 수 있다. 또는
EXPECT_NONFATAL_FAILURE(문장, 부분문자열); |
을 사용해 (EXPECT_* 등과 같이) 심각하지 않은 실패를 만드는지 확인할 수 있다.
기술적 이유로 몇 가지 단서가 있다.
- 실패 메시지를 두 매크로 중 어느 것으로도 스트림으로 전달할 수 없다.
- EXPECT_FATAL_FAILURE()의 문장에서는 비정적 지역 변수나 this 객체의 비정적 멤버를 참조할 수 없다.
- EXPECT_FATAL_FAILURE()의 문장에서는 값을 반환할 수 없다.
참고: 구글 테스트는 스레드를 염두에 두고 설계했다. <gtest/internal/gtest-port.h>에 있는 동기화 객체를 구현한다면 구글 테스트는 스레드 안전해지므로 여러 스레드에서 동시에 단정문을 사용할 수 있다. 하지만 구글 테스트는 단일 스레드 사용만 지원한다. 그러므로 스레드 안전하다면 EXPECT_FATAL_FAILURE()와 EXPECT_NONFATAL_FAILURE()에서는 현재 스레드에서만 실패를 잡을 수 있다. 문장에서 새 스레드를 만들고 그 스레드에서 실패가 발생하면 무시한다. 만약 모든 스레드에서 발생하는 실패를 잡아야 한다면 다음 매크로를 써야 한다.
EXPECT_FATAL_FAILURE_ON_ALL_THREADS(문장, 부분문자열); |
EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(문장, 부분문자열); |
현재 테스트 이름 얻기
간혹 함수에서 현재 실행 중인 테스트 이름을 알아야 할 수도 있다. 예를 들어 실행 중인 테스트 이름을 바탕으로 파일 이름을 설정하는데 테스트 픽스처의 SetUp() 메소드를 사용할 수도 있다. 이 정보는 ::testing::TestInfo 클래스에 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
namespace testing { class TestInfo { public: // Returns the test case name and the test name, respectively. // // Do NOT delete or free the return value - it's managed by the // TestInfo class. const char* test_case_name() const; const char* name() const; }; } // namespace testing |
현재 실행 중인 테스트에 대한 TestInfo 객체를 얻으려면 UnitTest 싱글턴 객체에서 current_test_info()를 호출한다.
1 2 3 4 5 6 |
// Gets information about the currently running test. // Do NOT delete the returned object - it's managed by the UnitTest class. const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); printf("We are in test %s of test case %s.\n", test_info->name(), test_info->test_case_name()); |
current_test_info()에서는 실행 중인 테스트가 없으면 널 포인터를 반환한다. 특히 TestCaseSetUp(), TestCaseTearDown() 또는 이 두 함수에서 호출한 함수에서 테스트 케이스를 찾을 수 없으면 그렇다.
사용 가능: 리눅스, 윈도, 맥
테스트 이벤트를 처리해 구글 테스트 확장하기
구글 테스트에서는 이벤트 리스너 API를 제공해 테스트 프로그램 진행과 테스트 실패에 대한 알림을 받을 수 있다. 이 이벤트에는 테스트 프로그램, 테스트 케이스, 테스트 메소드의 시작과 종료를 포함한다. 이 API를 사용해 표준 콘솔 출력을 보강 또는 대체하거나 XML 출력을 대체하거나, GUI 또는 데이터베이스와 같이 완전히 다른 출력 형식을 제공할 수 있다. 게다가 자원 누수 확인 프로그램을 구현하는데 테스트 이벤트를 확인 지점으로 사용할 수도 있다.
사용 가능: 리눅스, 윈도, 맥. 1.4.0판부터.
이벤트 리스너 정의하기
이벤트 리스너를 정의하려면 ::testing::TestEventListener나 ::testing::EmptyTestEventListener 중 하나를 상속한다. 앞에 것은 (추상) 인터페이스이므로 테스트 이벤트를 처리하려면 각 순수 가상 메소드를 오버라이드 해야 한다(예를 들면, 테스트를 시작하면 OnTestStart() 메소드가 호출된다). 뒤에 것은 인터페이스 내 모든 메소드에 대한 빈 구현을 제공하므로 하위 클래스에서는 관심 있는 메소드만 오버라이드 하면 된다.
이벤트가 생기면 처리 함수(handler function)에 인자로 컨텍스트를 전달하는데, 다음 인자 타입을 사용한다.
- UnitTest는 테스트 프로그램 전체 상태를 반영한다.
- TestCase에는 테스트 케이스에 대한 정보가 있으며, 테스트 케이스는 테스트를 하나 이상 포함한다.
- TestInfo에서는 테스트 상태에 대한 정보가 있으며
- TestPartResult는 테스트 단정문 결과를 나타낸다.
이벤트 처리 함수에서는 인자를 확인해 해당 이벤트와 테스트 프로그램 상태에 대한 정보를 확인한다. 다음은 그 예이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class MinimalistPrinter : public ::testing::EmptyTestEventListener { // Called before a test starts. virtual void OnTestStart(const ::testing::TestInfo& test_info) { printf("*** Test %s.%s starting.\n", test_info.test_case_name(), test_info.name()); } // Called after a failed assertion or a SUCCESS(). virtual void OnTestPartResult( const ::testing::TestPartResult& test_part_result) { printf("%s in %s:%d\n%s\n", test_part_result.failed() ? "*** Failure" : "Success", test_part_result.file_name(), test_part_result.line_number(), test_part_result.summary()); } // Called after a test ends. virtual void OnTestEnd(const ::testing::TestInfo& test_info) { printf("*** Test %s.%s ending.\n", test_info.test_case_name(), test_info.name()); } }; |
이벤트 리스너 사용하기
정의한 이벤트 리스너를 사용하려면 main() 함수에서 RUN_ALL_TESTS()를 호출하기 전에 구글 테스트 이벤트 리스너 목록(TestEventListeners 클래스로 나타내며 이름 끝에 ‘s’가 있다는 점에 주의한다)에 인스턴스를 추가한다.
1 2 3 4 5 6 7 8 9 |
int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); // Gets hold of the event listener list. ::testing::TestEventListeners& listeners = ::testing::UnitTest::GetInstance()->listeners(); // Adds a listener to the end. Google Test takes the ownership. listeners.Append(new MinimalistPrinter); return RUN_ALL_TESTS(); } |
문제가 하나 있다. 테스트 결과를 출력하는 기본 인쇄기가 여전히 영향을 미치므로 이 출력 결과와 MinimalistPrinter로 출력하는 결과가 함께 섞인다. 기본 인쇄기를 막으려면 다음 내용처럼 이벤트 리스너 목록에서 해제한 후 삭제하면 된다.
1 2 3 4 |
... delete listeners.Release(listeners.default_result_printer()); listeners.Append(new MinimalistPrinter); return RUN_ALL_TESTS(); |
이제 자리에 앉아 테스트 출력 결과가 완전히 달라진 것을 즐기면 된다. 더 자세한 내용은 이 예제를 참조한다.
리스너 하나 이상을 목록에 추가할 수 있다. On*Start()나 OnTestPartResult() 이벤트가 발생하면 목록에 있는 순서대로 리스너에서 그 이벤트를 받는다(새 리스너는 목록 끝에 추가 되므로 기본 문자 인쇄기와 기본 XML 생성기는 가장 먼저 이벤트를 받는다). On*End() 이벤트는 목록 반대 순서로 받는다. 이는 나중에 추가한 리스너 출력 결과를 앞에서 추가한 리스너 출력 결과에서 포함해 틀을 맞추기 위해서이다.
리스너에서 실패 만들기
이벤트를 처리할 때 (EXPECT_*(), ASSERT_*(), FAIL() 등) 실패를 일으키는 매크로를 사용할 수 있지만 몇 가지 제약이 있다.
- OnTestPartResult()에서는 어떤 실패도 만들면 안 된다(그렇지 않으면 OnTestPartResult()를 재귀적으로 호출한다).
- OnTestPartResult()를 처리하는 리스너에서는 어떤 실패도 만들면 안 된다.
목록에 리스너를 추가할 때는 실패를 일으킬 수 리스너 이전에 OnTestPartResult()를 처리하는 리스너를 둬야 한다. 이렇게 하면 뒤 리스너에서 실패가 생기더라도 앞에서는 테스트를 올바로 진행했다는 것을 보장할 수 있다.
실패를 일으키는 리스너에 대한 예제는 여기를 참조한다.
테스트 프로그램 실행하기: 고급 옵션
구글 테스트로 만든 테스트 프로그램은 일반적인 실행 파일이다. 빌드 후 직접 실행할 수 있고 실행할 때 추가한 환경 변수와 명령 행 플래그에 영향을 받는다. 플래그가 제대로 동작하려면 RUN_ALL_TESTS()를 호출하기 전에 ::testing::InitGoogleTest()를 호출해야 한다.
지원하는 플래그와 사용법을 보려면 테스트 프로그램에 –help 플래그를 붙여 실행하거나 간단히 -h, -? 또는 /?를 사용할 수도 있다. 이 기능은 1.3.0판부터 추가했다.
만약 환경 변수와 플래그 모두를 추가하면 플래그가 우선이다. 옵션 대부분은 코드에서 설정하거나 읽을 수 있으므로 명령 행 플래그 –gtest_foo 값을 읽어 ::testing::GTEST_FLAG(foo)처럼 쓸 수 있다. 플래그 기본 값을 바꾸는 일반적인 방식은 ::testing::InitGoogleTest()를 호출하기 전에 플래그 값을 설정하는 것이다.
1 2 3 4 5 6 7 8 9 |
int main(int argc, char** argv) { // Disables elapsed time by default. ::testing::GTEST_FLAG(print_time) = false; // This allows the user to override the flag on the command line. ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } |
테스트 선택하기
여기서는 어떤 테스트를 실행할지 고르는 다양한 선택 내용을 보여준다.
테스트 이름 나열하기
때로는 테스트를 실행하기 전에 프로그램에서 사용할 수 있는 테스트를 나열해야 하므로 필요하면 필터를 적용해야 한다. –gtest_list_tests 플래그는 다른 모든 플래그를 무시하고 다음 형식으로 테스트를 나열한다.
1 2 3 4 5 |
TestCase1. TestName1 TestName2 TestCase2. TestName |
이 플래그를 사용했을 때 나열된 테스트 중 어느 것도 실제 실행하지 않는다. 이 플래그에 해당하는 환경 변수는 없다.
사용 가능: 리눅스, 윈도, 맥
일부 테스트 실행하기
기본적으로 구글 테스트에서는 사용자가 정의한 모든 테스트를 실행한다. 하지만 변경 내용을 디버깅 하거나 빨리 검증하는 등의 이유로 일부 테스트만 실행하고 싶을 수도 있다. GTEST_FILTER 환경 변수나 –gtest_filter 플래그로 필터 문자열을 설정하면 구글 테스트에서는 전체 이름(TestCaseName.TestName 형식)이 필터와 일치하는 테스트만 실행한다.
필터 형식은 와일드카드(wildcard) 패턴을 ‘:’로 구분한 목록(포함(positive) 패턴)이며 선택적으로 ‘-‘ 다음에 ‘:’로 구분한 패턴 목록(제외(negative) 패턴)이 따라 올 수 있다. 포함 패턴 중 하나에 일치하나 제외 패턴 중 어느 것도 일치하지 않을 때 테스트는 필터와 일치한다.
패턴에는 ‘*’(모든 문자열과 일치)나 ‘?’(한 문자와 일치)를 포함할 수 있다. 편의상 필터 ‘*-NegativePatterns’는 ‘-NegativePatterns’로 쓸 수 있다.
예를 들면 다음과 같다.
- ./foo_test 플래그가 없으므로 모든 테스트를 실행한다.
- ./foo_test –gtest_filter=* 모든 것이 * 값에 일치하므로 모든 것을 실행한다.
- ./foo_test –gtest_filter=FooTest.* 테스트 케이스 FooTest에 있는 모든 것을 실행한다.
- ./foo_test –gtest_filter=-*DeathTest.* 종료 테스트를 빼고 모든 것을 실행한다.
- ./foo_test –gtest_filter=FooTest.*-FooTest.Bar 테스트 케이스 FooTest에서 FooTest.Bar만 빼고 모든 것을 실행한다.
사용 가능: 리눅스, 윈도, 맥
임시로 테스트 비활성화 하기
테스트가 망가졌지만 즉시 고칠 수 없을 때 DISABLED_ 접두어를 테스트 이름에 붙이면 실행할 때 그 것을 제외한다. 테스트를 비활성화 하더라도 여전히 컴파일 하므로(즉 부패하지 않는다) #if 0을 사용하거나 코드를 주석 처리하는 것보다 낫다.
테스트 케이스에 있는 모든 테스트를 비활성화 해야 한다면 DISABLED_를 각 테스트나 테스트 케이스 이름 앞에 붙일 수 있다.
예를 들면 다음 테스트는 컴파일은 하지만 구글 테스트에서 절대 실행하지 않는다.
1 2 3 4 5 6 7 |
// Tests that Foo does Abc. TEST(FooTest, DISABLED_DoesAbc) { ... } class DISABLED_BarTest : public ::testing::Test { ... }; // Tests that Bar does Xyz. TEST_F(DISABLED_BarTest, DoesXyz) { ... } |
참고: 이 기능은 잠시 고통을 덜고 싶을 때만 사용해야 한다. 비활성화 한 테스트는 여전히 나중에 고쳐야 한다. 이를 잊지 않도록 구글 테스트에서는 비활성화 한 테스트가 있으면 배너 경고를 출력한다.
팁: grep를 사용하면 비활성화 한 테스트 수를 쉽게 셀 수 있으며 이 수는 테스트 품질을 향상 시키는 지표로 사용할 수 있다.
사용 가능: 리눅스, 윈도, 맥
비활성화 한 테스트를 임시로 활성화 하기
테스트를 실행할 때 비활성화 한 테스트를 포함하려면 테스트 프로그램을 실행할 때 –gtest_also_run_disabled_tests 플래그를 사용하거나 GTEST_ALSO_RUN_DISABLED_TESTS 환경 변수를 0 이외 값으로 설정한다. –gtest_filter 플래그와 함께 사용해 비활성화 한 테스트 중에 선택할 수도 있다.
사용 가능: 리눅스, 윈도, 맥. 1.3.0판부터.
테스트 반복하기
이따금 테스트 결과가 제멋대로일 수도 있다. 어쩌면 시도한 횟수의 1%에서만 실패해 디버거로는 버그를 재현하기가 너무 어려울지도 모른다. 결국 이 때문에 큰 좌절감을 맛볼 수 있다.
–gtest_repeat 플래그를 사용하면 모든 (또는 선택한) 테스트 메소드를 여러 번 반복할 수 있다. 다행히 문제가 많은 테스트는 결국 실패해 디버그 할 수 있다. 사용법은 다음과 같다.
$ foo_test –gtest_repeat=1000 |
foo_test를 1000번 반복하고 실패해도 멈추지 않는다. |
$ foo_test –gtest_repeat=-1 |
반복 횟수가 음수이면 영원히 반복한다. |
$ foo_test –gtest_repeat=1000 –gtest_break_on_failure |
foo_test를 1000번 반복하고 첫 실패에서 멈춘다. 이는 디버거에서 실행할 때 유용하다. 테스트를 실패하면 디버거 상태가 되므로 변수와 스택을 살펴 볼 수 있기 때문이다. |
$ foo_test –gtest_repeat=1000 –gtest_filter=FooBar |
필터와 이름이 일치하는 테스트를 1000번 반복한다. |
테스트 프로그램에 AddGlobalTestEnvironment()를 사용해 전역으로 등록한 설정(set-up)/해제(tear-down) 코드가 있으면 여기에도 문제가 있을 수 있으므로 반복할 때마다 그 역시 반복한다. GTEST_REPEAT 환경 변수로 반복 횟수를 설정할 수도 있다.
사용 가능: 리눅스, 윈도, 맥
테스트 순서 섞기
–gtest_shuffle 플래그를 지정하거나 GTEST_SHUFFLE 환경 변수를 1로 설정해 테스트를 임의 순서로 실행할 수 있다. 이는 테스트 사이에 좋지 않은 의존성을 확인하는데 도움이 된다.
기본적으로 구글 테스트에서는 현재 시각을 바탕으로 계산한 임의 씨앗값(random seed)을 사용하므로 매번 다른 순서로 실행한다. 콘솔 출력 결과에는 임의 씨앗값이 있으므로 순서에 연관된 실패를 재현할 수도 있다. 임의 씨앗값을 명시적으로 지정하려면 –gtest_random_seed=SEED 플래그를 사용하거나 GTEST_RANDOM_SEED 환경 변수를 설정한다. 여기서 SEED는 0과 99999 사이 정수이다. 씨앗값이 0일 때는 현재 시각을 바탕으로 씨앗값을 계산하는 기본 동작을 한다.
–gtest_repeat=N과 함께 사용하면 반복할 때마다 임의 씨앗값을 얻고 테스트를 다시 섞는다.
사용 가능: 리눅스, 윈도, 맥. 1.4.0판부터.
테스트 출력 제어
여기서는 테스트 보고 결과를 조정하는 법을 알려준다.
색으로 표시한 터미널 출력
구글 테스트에서는 테스트를 구분하고 어느 것을 통과했는지 쉽게 알 수 있도록 터미널 출력 결과에 색을 사용해 표시한다.
GTEST_COLOR 환경 변수나 –gtest_color 명령 행 플래그를 yes, no 또는 auto(기본값)로 설정하면 색을 활성, 비활성 또는 구글 테스트에서 알아서 결정한다. 값이 auto이면 출력 결과를 터미널로 보내고 (윈도 환경이 아닐 때) TERM 환경 변수가 xterm이나 xterm-color일 때만 색을 사용한다.
사용 가능: 리눅스, 윈도, 맥
지연 시간 표시하지 않기
기본적으로 구글 테스트에서는 각 테스트를 실행하는데 걸린 시간을 표시한다. 이를 표시하지 않으려면 테스트 프로그램을 실행할 때 –gtest_print_time=0 명령 행 플래그를 사용한다. GTEST_PRINT_TIME 환경 변수를 0으로 설정해도 효과는 같다.
사용 가능: 리눅스, 윈도, 맥. (구글 테스트 1.3.0판 이하에서 기본 동작은 지연 시간을 표시하지 않는다)
XML 보고서 만들기
구글 테스트에서는 일반적인 텍스트 출력 결과에 추가로 상세한 XML 보고서를 만들 수 있다. 이 보고서에는 각 테스트를 실행한 기간이 나오므로 느린 테스트를 확인하는데 도움이 된다.
XML 보고서를 만들려면 GTEST_OUTPUT 환경 변수나 –gtest_output 플래그를 ‘xml:_path_to_output_file_’로 설정한다. 이 문자열을 설정하면 지정 위치에 파일을 만든다. 또한 간단히 ‘xml’로 지정하면 출력 결과를 현재 디렉터리에 test_detail.xml 파일로 만든다.
예를 들어 리눅스에서 ‘xml:output/directory/’ 또는 윈도에서 ‘xml:output\directory\’처럼 디렉터리를 지정하면 구글 테스트에서는 지정 위치에 XML 파일을 만들며, 파일 이름은 테스트 프로그램 이름으로 붙인다. 예를 들어 테스트 프로그램이 foo_test 또는 foo_test.exe이면 foo_test.xml이 된다. 해당 파일이 이미 있으면, 예를 들어 foo_test_1.xml과 같이 다른 이름을 선택해 덮어 쓰지 않도록 한다.
보고서 형식은 다음과 같다. junitreport Ant task에 기초하므로 허드슨(Hudson)과 같은 인기 있는 지속적인 빌드 시스템에서 사용할 수 있다. 그 형식은 원래 자바를 대상으로 한 것이라 구글 테스트에 적용하려면 다음처럼 약간 해석을 해야 한다.
1 2 3 4 5 6 7 8 9 |
<testsuites name="AllTests" ...> <testsuite name="test_case_name" ...> <testcase name="test_name" ...> <failure message="..."/> <failure message="..."/> <failure message="..."/> </testcase> </testsuite> </testsuites> |
- 루트 요소인 <testsuites>는 테스트 프로그램 전체에 해당한다.
- <testsuite> 요소는 구글 테스트에서 테스트 케이스에 해당한다.
- <testcase> 요소는 구글 테스트에서 테스트 함수에 해당한다.
예를 들어
1 2 3 |
TEST(MathTest, Addition) { ... } TEST(MathTest, Subtraction) { ... } TEST(LogicTest, NonContradiction) { ... } |
이 프로그램에 대한 보고서는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?xml version="1.0" encoding="UTF-8"?> <testsuites tests="3" failures="1" errors="0" time="35" name="AllTests"> <testsuite name="MathTest" tests="2" failures="1"* errors="0" time="15"> <testcase name="Addition" status="run" time="7" classname=""> <failure message="Value of: add(1, 1) Actual: 3Expected: 2" type=""/> <failure message="Value of: add(1, -1) Actual: 1Expected: 0" type=""/> </testcase> <testcase name="Subtraction" status="run" time="5" classname=""> </testcase> </testsuite> <testsuite name="LogicTest" tests="1" failures="0" errors="0" time="5"> <testcase name="NonContradiction" status="run" time="5" classname=""> </testcase> </testsuite> </testsuites> |
주의할 내용은 다음과 같다.
- <testsuites>나 <testsuite> 요소에서 tests 속성은 구글 테스트 프로그램이나 테스트 케이스에 테스트 함수가 얼마나 많은지 알려주며, failure 속성은 테스트 중 실패한 것이 얼마인지 알려준다.
- time 속성은 테스트, 테스트 케이스 또는 전체 테스트 프로그램 실행 기간을 밀리 초 단위로 표시한다.
- 각 <failure> 요소는 실패한 구글 테스트 단정문 하나에 대응한다.
- 일부 JUnit 개념은 구글 테스트에 적용되지 않았으며 여전히 DTD를 따른다. 그러므로 일부 더미 요소와 속성이 보고서에 있을 수 있는데 이는 무시해도 문제 없다.
사용 가능: 리눅스, 윈도, 맥
실패 보고 방법 제어하기
단정문 실패를 브레이크 지점으로 바꾸기
디버거에서 테스트 프로그램을 실행할 때, 단정문이 실패한 것을 디버거가 확인해 자동으로 사용자 입력을 받는 상태로 바뀐다면 매우 편리할 것이다. 구글 테스트에서는 실패 후 중지 모드(break-on-failure mode)로 이를 지원한다.
이를 활성화 하려면 GTEST_BREAK_ON_FAILURE 환경 변수를 0 이외 값으로 설정하거나 –gtest_break_on_failure 명령 행 플래그를 사용할 수 있다.
사용 가능: 리눅스, 윈도, 맥
예외로 발생하는 팝업 창을 없애기
윈도에서는 예외를 활성화 한 상태에서 구글 테스트를 사용할 수도 있다. 예외를 비활성화 했을 때조차 응용 프로그램에서는 여전히 구조화된 예외(SEH)를 발생할 수 있다. 테스트에서 예외를 발생하면 기본적으로 구글 테스트에서는 잡으려 하지 않는다. 대신 팝업 다이얼로그를 띄우므로 그 때 프로세스를 디버거에 연결해 무엇이 잘못됐는지 쉽게 찾을 수 있다.
하지만 테스트를 배치(batch job)로 실행하는 등의 이유로 팝업 창을 보고 싶지 않으면 GTEST_CATCH_EXCEPTIONS 환경 변수를 0이 아닌 값으로 설정하거나 –gtest_catch_exceptions 플래그를 사용한다. 그러면 구글 테스트에서는 테스트에서 발생한 모든 예외를 잡아 실패로 기록한다.
사용 가능: 윈도. 리눅스와 맥에서는 GTEST_CATCH_EXCEPTIONS와 –gtest_catch_exceptions을 사용해도 아무런 효과 없다. 심지어 예외를 활성화 하더라도 그렇다. 이 두 플랫폼에서 예외를 받도록 지원하는 것은 가능하지만 아직 구현하지 않았다.
다른 테스트 프레임워크 함께 사용하기
프로젝트에서 이미 다른 테스트 프레임워크를 사용하고 있는데 구글 테스트로 완전히 전환할 준비가 아직 안 됐다면 기존 테스트에 구슬 테스트 단정문을 사용해 구글 테스트에서 제공하는 많은 이점을 얻을 수 있다. 단지 main() 함수를 다음처럼 바꾸면 된다.
1 2 3 4 5 6 7 8 9 |
#include <gtest/gtest.h> int main(int argc, char** argv) { ::testing::GTEST_FLAG(throw_on_failure) = true; // Important: Google Test must be initialized. ::testing::InitGoogleTest(&argc, argv); ... whatever your existing testing framework requires ... } |
이렇게 한 후, 사용 중인 테스트 프레임워크에서 제공하는 단정문에 추가로 구글 테스트 단정문을 사용할 수 있다. 다음 예를 보자.
1 2 3 4 5 |
void TestFooDoesBar() { Foo foo; EXPECT_LE(foo.Bar(1), 100); // A Google Test assertion. CPPUNIT_ASSERT(foo.IsEmpty()); // A native assertion. } |
구글 테스트 단정문이 실패하면 오류 메시지를 출력하고 예외를 발생하는데, 이 예외는 기존 테스트 프레임워크에서는 실패로 처리한다. 만약 예외를 비활성화 해 코드를 컴파일 했다면 실패한 구글 테스트 단정문에서는 0이 아닌 코드 값으로 프로그램을 종료하고 테스트 러너(test runner)에는 테스트가 실패했음을 알린다.
::testing::GTEST_FLAG(throw_on_failure) = true;를 main()에 사용하는 대신 –gtest_throw_on_failure 플래그를 명령 행에 지정하거나 GTEST_THROW_ON_FAILURE 환경 변수를 0이 아닌 값으로 설정해도 된다.
사용 가능: 리눅스, 윈도, 맥. 1.3.0판부터.
테스트 함수를 여러 컴퓨터에 분산하기
테스트 프로그램을 실행할 수 있는 컴퓨터가 한 대 이상이라면 테스트 함수를 병렬로 실행해 결과를 더 빠르게 얻고 싶을 수 있다. 이런 기술을 조각 내기(sharding)이라 하며 각 컴퓨터를 조각(shard)이라 한다.
구글 테스트는 테스트 조각 내기와 호환된다. 이에 대한 이점을 얻으려면 테스트 러너(구글 테스트의 일부분이 아니다)를 다음처럼 해야 한다.
- 테스트를 실행할 여러 컴퓨터(조각)를 할당한다.
- 각 조각에서 GTEST_TOTAL_SHARDS 환경 변수를 총 조각 수로 설정한다. 이 값은 모든 조각에서 같아야 한다.
- 각 조각에서 GTEST_SHARD_INDEX 환경 변수를 해당 조각에 대한 인덱스로 설정한다. 각 조각은 서로 다른 인덱스로 설정해야 하고 범위는 [0, GTEST_TOTAL_SHARDS – 1]이다.
- 모든 조각에서 같은 테스트 프로그램을 실행한다. 구글 테스트에서는 위 두 가지 환경 변수를 확인하고 실행할 테스트 집합을 선택한다. 프로그램에 있는 각 테스트 함수는 모든 조각에 걸쳐 정확히 한 번만 실행한다.
- 모든 조각에서 실행을 마치길 기다린 후 결과를 수집해 보고한다.
프로젝트에 구글 테스트로 만들지 않은 테스트가 있으면 이 프로토콜을 이해하지 못한다. 테스트 조각 내기를 테스트 러너에서 인식하는지 확인하려면 GTEST_SHARD_STATUS_FILE 환경 변수를 존재하지 않는 파일 경로로 설정한다. 테스트 프로그램에서 조각 내기를 지원하면 확인으로 파일을 만들지만 (향후에는 유용한 정보를 넣을 수 있겠지만 지금은 실제 내용이 중요하지 않다) 그렇지 않으면 만들지 않는다.
좀 더 명확하게 다음 예를 보자. 테스트 프로그램 foo_test에 테스트 함수 5 개가 있고
1 2 3 4 5 |
TEST(A, V) TEST(A, W) TEST(B, X) TEST(B, Y) TEST(B, Z) |
컴퓨터는 3 대가 있다. 이 테스트 함수를 병렬로 실행하려면 GTEST_TOTAL_SHARD를 모든 컴퓨터에 3으로 설정하고 GTEST_SHARD_INDEX를 각각 0, 1, 2로 설정한다. 그런 후 각 컴퓨터에서 같은 foo_test를 실행한다.
조각 사이에 작업을 어떻게 분산할지 바꿀 권한은 구글 테스트에 있지만 가능한 시나리오 중 하나에 대한 예를 들어 보면 다음과 같다.
- 컴퓨터 #0에서는 A.V와 B.X를 실행한다.
- 컴퓨터 #1에서는 A.W와 B.Y를 실행한다.
- 컴퓨터 #2에서는 B.Z를 실행한다.
사용 가능: 리눅스, 윈도, 맥. 1.3.0판부터.
구글 테스트 소스 파일을 합치기
구글 테스트 구현 내용은 자체 테스트를 제외하고 파일 수가 약 30여 개이다. 때로는 새 컴퓨터에 쉽게 복사해 시작할 수 있도록 이를 파일 두 개(.h와 .cc)로 만들고 싶을 수도 있다. 실험 중이지만 scripts/ 디렉터리에서 파이썬 스크립인 fuse_gtest_files.py로 그렇게 할 수 있다(1.3.0판부터). 컴퓨터에는 파이썬 2.4이상을 설치해야 하며 그 디렉터리에서 실행만 하면 된다.
1 |
python fuse_gtest_files.py OUTPUT_DIR |
OUTPUT_DIR 디렉터리에 gtest/gtest.h와 gtest/gtest-all.cc 파일이 생기며, 이 파일에는 구글 테스트를 사용하는데 필요한 모든 내용이 있다. 이 파일을 원하는 곳에 복사하면 모든 준비는 끝난다. 이 파일로 테스트 프로그램을 컴파일하는 예제로 scripts/test/Makefile을 참조한다.
앞으로 가야 할 길
축하한다! 더 고급스런 구글 테스트 도구를 배웠고 더 복잡한 테스트 작업을 다룰 준비가 됐다. 더 깊이 알고 싶으면 FAQ를 읽어 본다.
[1] 성공인지 실패인지와 연관된 메시지를 포함한다.
[2] Unit of least precision이라고도 하며 부동소수점 수 사이에 거리를 뜻하며 수치 계산에서 정밀도 계산에 사용한다. http://en.wikipedia.org/wiki/Unit_in_the_last_place
[3] 명령 행 플래그인 –gtest_death_test_style로 초기화한다.
[4] 원문: statically-initialized modules may start threads before main is ever reached.
[5] http://code.google.com/p/googletest/source/browse/trunk/samples/sample7_unittest.cc, http://code.google.com/p/googletest/source/browse/trunk/samples/sample8_unittest.cc
[6] 바꿔 말하면 유효 범위가 같은 번역 단위 안이다.