입문: 구글 C++ 테스트 프레임워크 시작하기

updated Feb 24, 2011
원문: http://code.google.com/p/googletest/wiki/Primer


소개: 왜 구글 C++ 테스트 프레임워크인가?

C++ 테스트를 만드는데 구글 C++ 테스트 프레임워크를 사용해 많은 도움을 얻을 수 있다.

리눅스, 윈도 또는 맥 등 어디서 작업하든 관계 없이 C++ 코드를 만든다면 구글 테스트가 도움이 된다.

좋은 테스트란 무엇이고, 왜 구글 C++ 테스트 프레임워크가 그에 합당한지는 다음과 같다.

  1. 테스트는 독립적이고 반복할 수 있어야 한다. 어떤 테스트가 다른 테스트에 영향을 받아 성공 또는 실패할 때 그것을 디버깅하는 건 고통스럽다. 구글 C++ 테스트 프레임워크에서는 각 테스트를 서로 다른 객체에 대해 수행하며 격리시킨다. 테스트가 실패하면 구글 C++ 테스트 프레임워크에서는 재빨리 디버깅할 수 있도록 실패한 것을 격리시켜 실행할 수 있다.
  2. 테스트는 잘 조직할 수 있어야 하고 테스트하는 코드 구조를 잘 반영해야 한다. 구글 C++ 테스트 프레임워크에서는 관련 있는 테스트를 데이터와 서브루틴을 공유할 수 있는 테스트 케이스 그룹으로 만들 수 있다. 이를 통해 이해하기 쉽고 테스트를 유지 보수하기 쉽게 한다. 이런 일관성은 개발자가 다른 프로젝트에서 새 코드에 대해 일을 시작할 때 많은 도움이 된다.
  3. 테스트를 이식재사용을 할 수 있어야 한다. 오프 소스 커뮤니티에서는 많은 코드가 플랫폼 중립적이므로 테스트 역시 그래야 한다. 구글 C++ 테스트 프레임워크는 (gcc, MSVC 등) 다른 컴파일러를 사용하고 예외를 지원하거나 그렇지 않은 서로 다른 OS에서도 쓸 수 있으므로 다양한 상황에서 쉽게 쓸 수 있다.
  4. 테스트가 실패하면 문제에 대해 가능한 많은 정보를 제공해야 한다. 구글 C++ 테스트 프레임워크에서는 첫 번째 테스트가 실패했다고 멈추지 않는 대신, 현재 테스트만 멈추고 다음을 진행한다. 또한 현재 테스트를 계속 진행한 후 심각하지 않은 실패를 보고하도록 설정할 수 있다. 그러므로 실행, 편집, 컴파일 단계를 한 번만 거치더라도 여러 버그를 찾고 수정할 수 있다.
  5. 테스트 프레임워크는 테스트를 작업하는 사람이 쓸데 없는 일을 하지 않고 테스트 내용에만 집중할 수 있도록 해야 한다. 구글 C++ 테스트 프레임워크에서는 정의한 모든 테스트를 자동으로 추적하므로 실행할 테스트를 사용자가 일일이 지정하지 않아도 된다.
  6. 테스트는 빨라야 한다. 구글 C++ 테스트 프레임워크에서는 모든 테스트가 독립적이고, 공유한 자원을 테스트 사이에서 재사용할 수 있으며 단 한 번만 자원을 설정, 해제하도록 할 수도 있다.

구글 C++ 테스트 프레임워크는 인기있는 xUnit 구조에 기반하므로 JUnit이나 PyUnit을 사용해 봤다면 집에 있는 것처럼 편안하게 느낄 것이다. 사용해 보지 않았더라도 기초적인 내용을 배우고 사용하는데 10 분이면 충분하다. 그럼 시작해 보자!

참고: 종종 구글 C++ 테스트 프레임워크를 구글 테스트로 얘기한다.

새 테스트 프로젝트 준비

구글 테스트를 사용해 테스트 프로그램을 만들려면 구글 테스트를 컴파일해 라이브러리로 만든 후 링크해야 한다. 몇몇 많이 쓰는 빌드 시스템에 대해서는 빌드 파일이 있다. (구글 테스트 최상위 디렉터리에는 Visual Studio용으로 msvc/, 맥 Xcode용으로 xcode/, GNU make용으로 make/, 볼랜드 C++ 빌더용으로 codegear/ 등 여러 스크립트가 있다.) 사용하는 빌드 시스템이 이 목록에 없으면 구글 테스트를 어떻게 컴파일하는지 make/Makefile 내용을 살펴볼 수도 있다. (기본적으로 헤더 검색 경로에 GTEST_ROOT와 GTEST_ROOT/include를 포함해 src/gtest-all.cc를 컴파일해야 하며 여기서 GTEST_ROOT는 구글 테스트 최상위 디렉터리이다.)

구글 테스트 라이브러리를 컴파일한 후엔 테스트 프로그램 프로젝트를 만들어야 한다. 헤더 검색 경로에 GTEST_ROOT/include를 포함해 테스트를 컴파일할 때 컴파일러에서 <gtest/gtest.h>를 찾을 수 있도록 한다. 그리고 테스트 프로젝트에 구글 테스트 라이브러리를 링크한다.

여전히 궁금한 게 있으면 구글 테스트에 있는 자체 테스트가 어떻게 구성되어 있는지 살펴보고 예로 사용하자.

기본 개념

구글 테스트에서는 단정문을 먼저 만든다. 단정문은 조건이 참인지 확인하는 문장이다. 단정문 결과는 성공, 심각하지 않은 실패 또는 심각한 실패 중 하나이다. 심각한 실패일 때는 현재 함수를 멈추고 그렇지 않으면 일반적으로 계속 진행한다.

테스트에서는 단정문을 사용해 테스트한 코드의 동작을 검증한다. 테스트가 실행 중 충돌하거나(crash) 단정문을 실패하면 결과는 실패이고 그렇지 않으면 성공이다.

테스트 케이스는 하나 이상의 테스트로 구성한다. 테스트를 만들 때는 테스트할 코드 구조를 반영하는 테스트 케이스로 그룹을 만드는 게 좋다. 테스트 케이스 하나에 있는 여러 테스트가 공통 객체와 서브루틴을 공유해야 한다면 그런 테스트는 테스트 픽스처(test fixture) 클래스로 옮길 수 있다.

테스트 프로그램에는 여러 테스트 케이스로 구성할 수 있다.

지금부터는 테스트 프로그램을 어떻게 만드는지 설명할 텐데 먼저 각 단정문 수준, 테스트와 테스트 케이스를 만드는 것부터 시작한다.

단정문

구글 테스트 단정문은 함수 호출과 비슷한 매크로이며 클래스나 함수 동작에 대한 단정문을 만들어 테스트한다. 단정문이 실패하면 구글 테스트에서는 해당 단정문이 있는 소스 파일과 줄 번호를 실패 메시지와 함께 출력한다. 또한 직접 정의한 실패 메시지를 구글 테스트 기본 메시지에 추가할 수도 있다.

단정문은 같은 대상을 테스트하지만 결과가 다른 것이 쌍으로 있다. ASSERT_* 형식은 실패하면 심각한 실패가 되고 현재 함수를 중지한다. 그에 반해 EXPECT_* 형식은 심각하지 않은 실패가 되므로 현재 함수를 중지하지 않는다. 테스트 하나에서 실패를 하나 이상 확인할 수 있으므로 보통은 EXPECT_*가 더 낫다. 하지만 단정문을 실패했을 때 계속 진행하는 게 적절하지 않으면 ASSERT_*를 사용해야 한다.

ASSERT_*는 실패하면 현재 함수에서 즉시 빠져 나가므로 그 다음에 자원을 해제하는 코드가 있으면 실행하지 않을 수 있어 자원 누수가 생길 수 있다. 누수가 있다면 그 특성에 따라 단정문 오류에 추가로 힙 오류 확인을 해 누수를 수정해야 할지 그럴 필요가 없는지 판단한다.

사용자 정의 실패 메시지를 사용하려면 << 연산자를 사용해 해당 매크로에 스트림을 전달한다. 예를 들면,

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x&#91;i&#93;, y&#91;i&#93;) << "Vectors x and y differ at index " << i;
}
&#91;/sourcecode&#93;

특히 C 문자열과 string 객체를 비롯해 ostream에 스트림으로 전달할 수 있는 것은 모두 단정문 매크로에 전달할 수 있다. 만약 와이드 문자열(wchar_t*, 윈도 유니코드 모드에서 TCHAR*, 또는 std::wstring)을 단정문에 스트림으로 전달하면 출력할 때 UTF-8로 변환된다.
<h2><strong>기본 단정문</strong></h2>
다음 단정문은 기본적인 참/거짓 조건을 테스트한다.
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="205"><strong>심각한 실패 단정문</strong></td>
<td valign="top" width="205"><strong>심각하지 않은 실패 단정문</strong></td>
<td valign="top" width="205"><strong>검증 내용</strong></td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_TRUE(<em>조건</em>);</td>
<td valign="top" width="205">EXPECT_TRUE(<em>조건</em>);</td>
<td valign="top" width="205"><em>조건</em>은 참</td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_FALSE(<em>조건</em>);</td>
<td valign="top" width="205">EXPECT_FALSE(<em>조건</em>);</td>
<td valign="top" width="205"><em>조건</em>은 거짓</td>
</tr>
</tbody>
</table>
이 단정문이 실패했을 때 ASSERT_*은 심각한 실패가 되므로 현재 함수에서 빠져 나가는 반면, EXPECT_*는 심각하지 않은 실패이고 함수를 계속 진행한다는 점을 기억한다. 다만 어느 경우든 단정문 실패는 테스트를 실패한 것을 나타낸다.

<em>사용 가능</em>: 리눅스, 윈도, 맥
<h2><strong>이항 비교(Binary Comparison)</strong></h2>
여기서는 두 값을 비교하는 단정문을 설명한다.
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="205"><strong>심각한 실패 단정문</strong></td>
<td valign="top" width="205"><strong>심각하지 않은 실패 단정문</strong></td>
<td valign="top" width="205"><strong>검증 내용</strong></td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_EQ(기대값, 실제값);</td>
<td valign="top" width="205">EXPECT_EQ(기대값, 실제값);</td>
<td valign="top" width="205">기대값 == 실제값</td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_NE(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205">EXPECT_NE(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205"><em>val1</em> != <em>val2</em></td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_LT(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205">EXPECT_LT(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205"><em>val1</em> &lt; <em>val2</em></td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_LE(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205">EXPECT_LE(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205"><em>val1</em> &lt;= <em>val2</em></td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_GT(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205">EXPECT_GT(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205"><em>val1</em> &gt; <em>val2</em></td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_GE(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205">EXPECT_GE(<em>val1</em>, <em>val2</em>);</td>
<td valign="top" width="205"><em>val1</em> &gt;= <em>val2</em></td>
</tr>
</tbody>
</table>
실패하면 구글 테스트에서는 <em>val1</em>과 <em>val2</em>를 모두 출력한다. (나중에 소개할 모든 상등 연산자를 포함해) ASSERT_EQ*와 EXPECT_EQ*에서는 <em>실제값</em> 부분에 테스트할 표현식을 넣고 바라는 값을 기대값 부분에 넣어야 하며 구글 테스트에서 실패 메시지는 이에 맞춰 표시한다.

값 인자는 단정문의 비교 연산자로 비교할 수 있어야 하며 그렇지 않으면 컴파일러 오류가 발생한다. 또한 값은 ostream에 전달할 수 있도록 &lt;&lt; 연산자를 지원해야 한다.

이 단정문은 ==, &lt; 등 해당 비교 연산자에 대한 연산을 정의하면 사용자 정의 타입에도 사용할 수 있다. 해당 연산자에 대해 정의했다면 ASSERT_*() 매크로를 사용하는 게 더 좋은데, 이 매크로는 비교 결과와 더불어 두 피연산자도 출력하기 때문이다.

인자는 항상 정확히 한 번만 평가하므로 해당 인자에 부수 효과(side effect)가 있더라도 <strong>괜찮다</strong>. 하지만 일반적인 C/C++ 함수에서 인자 평가 순서는 정해져 있지 않으므로 특정 인자 평가 순서에 의존해서 코드를 만들지 않아야 한다.

포인터에 대해 ASSERT_EQ()는 포인터가 같은지 평가하므로, 만약 두 C 문자열에 사용하면 값이 같은지 확인하는 게 아니라 메모리 위치가 같은지 확인한다. C 문자열 값을 비교하려면 이후에 소개할 ASSERT_STREQ()를 사용해야 한다. 특히 C 문자열이 NULL인지 확인할 때는 ASSERT_STREQ(NULL, C 문자열) 형식으로 사용한다. 하지만 string 객체를 비교할 때는 ASSERT_EQ를 사용해야 한다.

여기서 보여준 매크로는 string과 wstring 같은 일반(narrow)과 와이드 문자열 모두에서 잘 동작한다.

<em>사용 가능</em>: 리눅스, 윈도, 맥
<h2><strong>문자열 비교</strong></h2>
다음 단정문은 두 <strong>C 문자열</strong>을 비교한다. string 객체를 비교하려면 앞에서 살펴 본 EXPECT_EQ, EXPECT_NE 등을 대신 사용한다.
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="205"><strong>심각한 실패 단정문</strong></td>
<td valign="top" width="205"><strong>심각하지 않은 실패 단정문</strong></td>
<td valign="top" width="205"><strong>검증 내용</strong></td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_STREQ(<em>기대한 문자열</em>, <em>실제 문자열</em>);</td>
<td valign="top" width="205">EXPECT_STREQ(<em>기대한 문자열</em>, <em>실제 문자열</em>);</td>
<td valign="top" width="205">두 C 문자열 내용이 같다.</td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_STRNE(<em>str1</em>, <em>str2</em>);</td>
<td valign="top" width="205">EXPECT_STRNE(<em>str1</em>, <em>str2</em>);</td>
<td valign="top" width="205">두 C 문자열 내용이 다르다.</td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_STRCASEEQ(<em>기대한 문자열</em>, <em>실제 문자열</em>);</td>
<td valign="top" width="205">EXPECT_STRCASEEQ(<em>기대한 문자열</em>, <em>실제 문자열</em>);</td>
<td valign="top" width="205">대소문자 구별하지 않으며, 두 C 문자열 내용이 같다.</td>
</tr>
<tr>
<td valign="top" width="205">ASSERT_STRCASENE(<em>str1</em>, <em>str2</em>);</td>
<td valign="top" width="205">EXPECT_STRCASENE(<em>str1</em>, <em>str2</em>);</td>
<td valign="top" width="205">대소문자 구별하지 않으며, 두 C 문자열 내용이 다르다.</td>
</tr>
</tbody>
</table>
단정문에서 ‘CASE’란 이름은 대소문자를 구별하지 않는다는 뜻에 주의한다.

*STREQ*와 *STRNE*는 와이드 C 문자열(wchar_t*)에도 사용할 수 있다. 만약 두 와이드 문자열을 비교해 실패하면 그 값은 UTF-8 일반 문자열로 출력한다.

NULL 포인터와 빈 문자열은 서로 <em>다른 것</em>으로 간주한다.

<em>사용 가능</em>: 리눅스, 윈도, 맥

<em>참조</em>: (부분 문자열, 접두/접미, 그리고 정규 표현식 등) 더 많은 문자열 비교 방법은 고급 안내문을 참조한다.
<h2><strong>간단한 테스트</strong></h2>
테스트를 만들려면,
<ol>
	<li>TEST() 매크로를 사용해 테스트 함수를 정의하고 이름을 준다. 테스트는 값을 반환하지 않는 일반적인 C++ 함수이다.</li>
	<li>이 테스트 함수 안에서는 유효한 모든 C++ 문장을 포함해, 값을 확인하기 위한 구글 테스트의 다양한 단정문을 사용한다.</li>
	<li>테스트 결과는 단정문으로 확인한다. 테스트에 있는 어떤 단정문이 (심각하든 그렇지 않든) 실패하거나 테스트가 충돌하면(crash) 전체 테스트는 실패하고 그렇지 않으면 성공이다.</li>
</ol>
<div>
TEST(test_case_name, test_name) {
... test body ...
}

TEST() 인자는 일반적인 것에서 특정한 것 순이다. 첫 번째 인자는 테스트 케이스 이름이며 두 번째는 테스트 케이스에서 테스트 이름이다. 테스트 케이스는 개별 테스트를 포함할 수 있다는 점을 기억하자. 테스트 전체 이름(full name)은 해당 테스트를 포함하는 테스트 케이스와 테스트 자체 이름으로 구성된다. 다른 테스트 케이스에 있는 테스트의 이름은 서로 같을 수 있다.

예를 들어 간단한 정수 함수가 있다고 생각해 보자.

int Factorial(int n); // Returns the factorial of n

이 함수에 대한 테스트 케이스는 다음과 같을 수 있다.

// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(1, Factorial(0));
}

// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

구글 테스트에서는 테스트 케이스별로 테스트 결과를 그룹으로 만들므로 논리적으로 관계 있는 테스트는 같은 테스트 케이스에 두는 게 좋다. 다시 말해 TEST() 첫 인자가 같아야 한다. 위 예에서 테스트는 HandlesZeroInput과 HandlesPositiveInput 두 개이며 같은 테스트 케이스인 FactorialTest에 속한다.

사용 가능: 리눅스, 윈도, 맥

테스트 픽스처: 여러 테스트에 같은 데이터 설정 사용하기

비슷한 데이터에 대해 연산하는 테스트를 하나 이상 작성한다면 테스트 픽스처를 사용할 수 있다. 이를 통해 다른 테스트에서 같은 객체 설정을 다시 사용할 수 있다.

픽스처를 만들려면

  1. 클래스를 ::testing::Test에서 파생하고 하위 클래스에서 픽스처 멤버에 접근할 수 있도록 본체는 protected: 또는 public:으로 시작한다.
  2. 클래스 안에 사용할 객체를 선언한다.
  3. 필요하면 각 테스트에 대해 객체를 준비하도록 기본 생성자나 SetUp() 함수를 작성한다. 흔히 SetUp()을 Setup()처럼 소문자 u로 적는 실수를 많이 하므로 주의한다.
  4. 필요하면 SetUp()에서 할당한 자원을 해제하도록 소멸자나 TearDown() 함수를 작성한다. 생성자/소멸자와 SetUp()/TearDown() 중 어떤 때 어느 것을 써야 하는지는 FAQ를 살펴본다.
  5. 필요하면 공유할 테스트에 대해 서브루틴을 정의한다.

픽스처를 사용할 때는 TEST() 대신 TEST_F()를 사용해야 테스트 픽스처에 있는 객체와 서브루틴에 접근할 수 있다.

TEST_F(test_case_name, test_name) {
 ... test body ...
}

TEST()와 마찬가지로 첫 인자는 테스트 케이스 이름이지만 TEST_F()에서는 테스트 픽스처 클래스 이름이어야 한다. _F가 픽스처를 나타내는 걸 알 수 있을 거다.

불행히도 C++ 매크로 시스템에서는 여러 테스트에 대한 모든 타입을 다룰 수 있는 매크로를 하나로 만들 수 없으므로 잘못된 매크로를 사용하면 컴파일러 오류가 발생한다.

게다가 TEST_F()에서 사용할 픽스처 클래스는 사용하기 전에 먼저 정의해야 하며 그렇지 않으면 ‘virtual outside class declaration’ 컴파일러 오류가 발생한다.

구글 테스트에서는 TEST_F()를 정의한 테스트에 대해

  1. 실행 중에 새로운 테스트 픽스처를 만들고
  2. SetUp()에서 픽스처를 즉시 초기화하며
  3. 테스트를 실행한 후
  4. TearDown()을 호출해 정리하고
  5. 테스트 픽스처를 삭제한다. 같은 테스트 케이스에 있는 각 테스트에는 다른 테스트 픽스처 객체가 있으며, 구글 테스트에서는 항상 다음 것을 생성하기 전에 해당 테스트 픽스처를 삭제한다. 구글 테스트에서는 여러 테스트에 대해 같은 테스트 픽스처를 재사용하지 않으며, 어떤 테스트에서 픽스처에 변경한 것은 무엇이든 다른 테스트에 영향을 주지 않는다.

예를 들어 FIFO 큐 클래스인 Queue에 대해 테스트를 만든다고 하자. 클래스 인터페이스는 다음과 같다.

template <typename E> // E is the element type.
class Queue {
 public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue(); // Returns NULL if the queue is empty.
  size_t size() const;
  ...
};

먼저 픽스처 클래스를 정의한다. 관례에 따라 테스트할 클래스가 Foo라면 이름을 FooTest라고 하는 게 좋다.

class QueueTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  // virtual void TearDown() {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

이 예에서는 소멸자에서 처리되는 것 외에 각 테스트를 마친 후 따로 정리할 것이 없으므로 TearDown()은 없어도 된다.

이제 TEST_F()와 픽스처를 사용해 테스트를 만든다.

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(0, q0_.size());
}

TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(NULL, n);

  n = q1_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0, q1_.size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1, q2_.size());
  delete n;
}

위 예에서는 ASSERT_*와 EXPECT_* 단정문을 모두 사용한다. 가장 중요한 규칙은 단정문을 실패한 후에도 더 많은 오류를 찾으려고 테스트를 계속 진행하고 싶으면 EXPECT_*를, 계속 진행하는 게 상식적으로 맞지 않으면 ASSERT_*를 사용하는 것이다. 예를 들어 Dequeue 테스트에 있는 두 번째 단정문은 ASSERT_TRUE(n != NULL) 인데, 포인터 n은 나중에 역참조해야 하므로 n이 NULL이면 메모리 보호 오류가 발생하기 때문이다.

위 테스트를 실행할 때는 다음과 같은 일이 발생한다.

  1. 구글 테스트에서는 QueueTest 객체(앞으로 t1이라 부른다)를 생성한다.
  2. t1.SetUp()에서 t1을 초기화한다.
  3. 첫 번째 테스트(IsEmptyInitially)에서 t1에 대해 실행한다.
  4. 테스트를 마친 후 t1.TearDown()에서 정리한다.
  5. t1은 소멸한다.
  6. 이번에는 DequeueWorks 테스트를 실행하며 다른 QueueTest 객체에 대해 위 과정을 반복한다.

사용 가능: 리눅스, 윈도, 맥

참조: 구글 테스트에서는 테스트 객체를 생성할 때 모든 구글 테스트 플래그를 저장하고 객체가 소멸할 때 다시 복원한다.

테스트 호출하기

TEST()와 TEST_F()는 테스트를 암시적으로 구글 테스트에 등록하므로 다른 C++ 테스트 프레임워크와 달리 테스트를 실행하려고 다시 나열할 필요가 없다.

테스트를 정의한 후 RUN_ALL_TESTS()를 호출해 테스트를 실행할 수 있는데 모든 테스트가 성공이면 0을, 그렇지 않으면 1을 반환한다. RUN_ALL_TESTS()는 링크 단위에 있는 모든 테스트를 실행하는데, 링크 단위는 테스트 케이스나 소스 파일과는 다를 수 있다.

RUN_ALL_TESTS() 매크로를 호출하면

  1. 모든 구글 테스트 플래그 상태를 저장한다.
  2. 첫 번째 테스트에 대해 테스트 픽스처 객체를 생성한다.
  3. 만든 객체를 SetUp()에서 초기화한다.
  4. 픽스처 객체에 대해 테스트를 실행한다.
  5. TearDown()에서 해당 픽스처를 정리한다.
  6. 해당 픽스처를 삭제한다.
  7. 모든 구글 테스트 플래그 상태를 복원한다.
  8. 모든 테스트를 마칠 때까지 다음 테스트에 대해 위 과정을 반복한다.

이에 추가로 단계 2에서, 테스트 픽스처의 생성자에서 심각한 실패가 발생하면 단계 3~5는 건너뛴다. 마찬가지로 단계 3에서 심각한 실패가 발생하면 단계 4를 건너뛴다.

중요: RUN_ALL_TESTS() 반환값은 무시하지 않아야 하며 그렇게 하면 gcc에서는 컴파일러 오류가 발생한다. 자동화된 테스트 서비스에서는 stdout/stderr 출력이 아니라 종료 코드로 테스트 통과 여부를 결정해야 하므로 이는 합리적이다. 그러므로 main() 함수에서는 반드시 RUN_ALL_TESTS() 값을 반환해야 한다.

그리고 RUN_ALL_TESTS()는 단 한 번만 호출해야 한다. 여러 번 호출하면 일부 고급 구글 테스트 기능과 충돌하므로 그런 방식은 지원하지 않는다.

사용 가능: 리눅스, 윈도, 맥

main() 함수 작성하기

아래와 같은 틀을 참고해 시작할 수 있다.

#include "this/package/foo.h"
#include <gtest/gtest.h>

namespace {

// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
 protected:
  // You can remove any or all of the following functions if its body
  // is empty.

  FooTest() {
    // You can do set-up work for each test here.
  }

  virtual ~FooTest() {
    // You can do clean-up work that doesn't throw exceptions here.
  }

  // If the constructor and destructor are not enough for setting up
  // and cleaning up each test, you can define the following methods:

  virtual void SetUp() {
    // Code here will be called immediately after the constructor (right
    // before each test).
  }

  virtual void TearDown() {
    // Code here will be called immediately after each test (right
    // before the destructor).
  }

  // Objects declared here can be used by all tests in the test case for Foo.
};

// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
  const string input_filepath = "this/package/testdata/myinputfile.dat";
  const string output_filepath = "this/package/testdata/myoutputfile.dat";
  Foo f;
  EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}

// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
  // Exercises the Xyz feature of Foo.
}

}  // namespace

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

::testing::InitGoogleTest() 함수에서는 구글 테스트 플래그에 대해 명령 행을 파싱하고 인식한 플래그를 모두 제거하므로 사용자는 이를 통해 테스트 프로그램에 다양한 플래그를 사용해 동작을 제어할 수 있다. 플래그는 고급 안내문에서 다룬다. 이 함수는 RUN_ALL_TESTS()를 호출하기 전에 호출해야 하며 그렇지 않으면 플래그를 적절히 초기화할 수 없다.

윈도에서는 InitGoogleTest()를 와이드 문자열에도 사용 할 수 있으므로 UNICODE 모드로 컴파일 하는 프로그램에서도 사용할 수 있다.

아마 main() 함수를 항상 이렇게 만드는 건 일이 너무 많다고 생각할 수도 있다. 그 생각에 전적으로 동의하기 때문에 구글 테스트에서는 main() 함수에 대한 기본 구현을 제공한다. 그 내용이 적당하다면 만든 테스트를 gtest_main 라이브러리에 링크만 하면 된다.

Visual C++ 사용자들이 주의해야 할 중요한 점

테스트를 라이브러리에 넣고 main() 함수를 다른 라이브러리나 .exe 파일에 두면 테스트를 실행할 수 없다. 이는 Visual C++ 버그 때문이다. 테스트를 정의하면 구글 테스트에서는 이를 등록하려고 정적 객체를 생성한다. 이 객체를 참조하는 곳은 없으나 객체의 생성자는 여전히 실행한다. 하지만 라이브러리 내용을 참조하는 곳이 없으므로 Visual C++ 링커에서는 해당 라이브러리를 제거한다. 주 프로그램에서는 라이브러리에 있는 테스트를 참조해야 하므로 링커가 해당 라이브러리를 무시하지 않도록 해야 한다. 그러므로 다음과 같이 라이브러리 코드 어디서든 다음 함수를 선언한다.

__declspec(dllexport) int PullInMyLibrary() { return 0; }

DLL이 아닌 정적 라이브러리에 테스트를 두면 __declspec(dllexport)는 필요 없다. 그리고 주 프로그램에서는 위 함수를 호출하는 코드를 작성한다.

int PullInMyLibrary();
static int dummy = PullInMyLibrary();

이렇게 하면 테스트를 참조할 수 있도록 유지하고 실행할 때 테스트를 등록한다.

이에 추가로, 정적 라이브러리에 테스트를 정의하면 주 프로그램에 /OPT:NOREF 링커 옵션을 추가해야 한다. MSVC++ IDE를 사용한다면 .exe 프로젝트 속성/속성 설정(Configuration Properties)/링커(Linker)/최적화(Optimization)으로 가서 참조하지 않는 데이터를 유지하도록 참조 설정을 /OPT:NOREF로 설정한다.

쉽게 빠질 수 있는 함정이 한 가지 더 있다. 구글 테스트를 (gtest.vcproj에 정의한 대로) 정적 라이브러리로 사용하면 테스트 역시 정적 라이브러리에 있어야 한다. 테스트를 DLL에 둔다면 구글 테스트 역시 DLL로 만들어야 한다. 그렇지 않으면 테스트를 올바르게 실행할 수 없거나 아예 실행하지 못한다. 일반적인 결론은 바로 이렇다. 삶을 더 편하게 해라. 테스트를 라이브러리로 만들지 말아라!

앞으로 가야 할 길

축하한다! 구글 테스트 기본을 배웠다. 이제 구글 테스트로 테스트를 만들고 실행할 수 있으며 몇 가지 예제 프로그램을 실행해 보거나 구글 테스트의 더욱 유용한 기능을 설명한 고급 안내문을 계속 살펴볼 수도 있다.

알고 있는 제약

구글 테스트는 스레드에 안전하게 설계했지만 pthreads 라이브러리를 사용할 수 있는 시스템에서 그렇다. 현재 윈도 등 스레드를 두 개 이상 동시에 실행할 수 있는 시스템에서 구글 테스트 단정문은 불안전하다. 대부분의 테스트에서 단정문은 주 스레드에서 실행하므로 일반적으로 이는 문제가 아니다. 만약 도움을 주고 싶다면 자신이 사용하는 플랫폼에 대한 필수 동기화 객체(the necessary synchronization primitives)를 gtest-port.h에 구현하기 위해 지원할 수 있다.

You may also like...