Google C++ Testing Framework 시작하기
Introduction
Google C++ Testing Framework(이하 google test)은 더 나은 C++ test를 위한 tool이다. 또 Linux, Mac, Windows 모두에서 사용 가능하다.
좋은 test를 위해 Google test가 초점을 맞추고 있는 내용은 아래와 같다.
1. Test는 독립적이어야 하고 반복가능해야 한다.
Success 혹은 Fail인 test를 debug 하는 일은 힘든 일이라 google test는 test를 별도의 object로 분리해 독립적으로 수행되도록 만든다. Test fail인 경우 google test에서는 실패한 case만 별도로 수행해 빠른 debugging이 가능하도록 지원한다.
2. Test는 잘 조직되어 있어야 하고, test되는 code의 구조를 잘 반영하고 있어야만 한다.
Google test는 같이 공유할 수 있는 data와 subroutine을 가진 testcase들로 구성하는 것이 가능하다. 이러한 공통적인 패턴들이 test를 이해하기 쉽게 하고 유지보수가 용이하게 만들어준다.
3. Test는 portable 해야하며 재사용이 가능해야 한다.
Open source community는 플랫폼에 종속적인 많은 source code들을 보유하고 있으며 이들을 test하기 위해서는 test 또한 특정 플랫폼에 종속적이어야 한다. 그래서 google test는 다양한 OS와 compiler를 지원하며 다양한 설정으로 동작이 가능하다.
4. Test가 실패했을 때, 문제에 대한 정보들을 가능한 많이 제공해야 한다.
Google test는 첫번째 실패 시점에서 멈추지 않는다. 대신에 현재의 test는 중단시키고, 다음의 test로 계속 진행되도록 한다. 현재의 test가 지나간 후에 치명적이지 않은 실패에 대해 report 하도록 설정하는 것이 가능해서 여러개의 bug들을 발견하고 수정할 수 있다.
5. Testing framework은 test 작성자들을 다른 일들에서 해방시키고 test 내용에만 집중할 수 있게 만들어줘야 한다.
Google test는 이미 정의된 test들은 유지해 두기 때문에 test 실행을 위해 사용자들이 또 다시 나열할 필요가 없도록 한다.
6. Test는 빨라야만 한다.
Google test를 사용하면 test 사이에서의 공유된 자원들을 재사용할 수 있다.
Google test는 xUnit architecture 기반으로 만들어졌기 때문에 JUnit이나 PyUnit을 사용할 때와 비슷하여 빠르게 적용하기 좋다.
Setting up a New Test Project
Google test를 사용하기 위해선 Google test를 compile 해서 사용자의 test에 library로 link 해야만 하며, 자주 사용되는 build system에 대한 build file을 제공하고 있다.(msvc, xcode, make, codegear, scons directory) 만약 기본으로 제공되는 것과 다른 system을 사용하고 있다면 make/Makefile을 참조하면 되는데, 기본적으로 src/gtest-all.cc와 GTEST_ROOT, GTEST_ROOT/include를 compile 해야만 한다.(GTEST_ROOT : Google test root directory)
Google test library를 compile 할 수 있게 되었다면 project를 생성해서 사용자의 test program을 위한 build target을 수행해야만 한다. 또 사용자의 test를 compile 할 때, compiler가 gtest/gtest.h를 찾을 수 있게 GTEST_ROOT/include 를 포함해야만 한다.
Basic Concepts
Google test를 사용할 때 condition이 true인지를 확인하는 assertion을 사용하는 것으로 시작하는 것이 좋다. Assertion의 결과는 success, nonfatal failure, fatal failure 중에 하나가 될 수 있으며, fatal failure가 발생했다면 현재의 function은 중단될 것이다. (하지만 전체 program은 정상적으로 진행됨)
Testcase는 하나 이상의 test로 구성될 수 있는데 사용자는 code의 구조를 반영할 수 있도록 여러개의 testcase들로 grouping이 가능하며, 복수의 test가 common object나 subroutine을 공유해야 한다면 그런 것들을 test fixture class에 넣어두고 사용할 수 있다.
Assertions
Google test의 assertion은 function call로 구성된 macro이다. Assertion이 fail로 끝나게 되면, google test는 assertion의 source file, line number, failure message 들을 화면에 출력한다.
Assertion은 같지만 현재의 function은 다르게 처리하는 function 들의 짝으로 구성되어 있는데, ASSERT_XXX 의 형태로 구성된 assertion은 fatal failure를 발생시켜 현재의 function을 중단시키며, EXPECT_XXX 형태의 assertion은 nonfatal failure를 발생시키고 현재 수행중인 function을 중단시키지 않는다.
Custom failure message 를 사용하려면, '<<' 연산자를 이용해서 해결할 수 있다. 예제는 아래와 같다.
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[i], y[i]) << "Vectors x and y differ at index " << i;
}
만약 wchar_t*, TCHAR* 등의 wide string을 사용하고 있다면, 출력되면서 자동으로 UTF-8로 변환된다.
Basic Assertions
앞에서도 언급되었지만 ASSERT_XXX는 fatal failure를 발생시키며, EXPECT_XXX는 nonfatal failure를 발생시킨다.
Binary Comparison
여기에서는 두 값을 비교하는 assertion 구문들을 설명한다.
실패했을 경우 google test는 두 가지 값 val1, val2를 출력하게 되는데 사용자가 test 하고자 하는 구문은 actual에, expected value는 expected 항목에 넣어주어야 한다.
이러한 assertion 구문들은 사용자 정의 operator들에 대해서도 동작이 가능한데, 만약 사용자 정의 연산자들을 사용하게 된다면 ASSERT_XXX 구문을 사용하는 것이 좋다. 왜냐하면 ASSERT_XXX 구문은 비교 결과 뿐만 아니고, 두 개의 operand 들도 화면에 출력해주기 때문이다.
ASSERT_EQ에서 pointer를 사용한다면 pointer의 일치여부를 테스트하게 된다. 만약 두 개의 문자열을 사용했다면, 두 문자열의 값이 일치하느냐를 테스트하지 않고 동일한 memory location인지를 판단해서 결과를 내보낸다. 만약에 문자열 값의 일치여부를 테스트하고 싶다면 ASSERT_EQ를 사용해서는 안되고 대신에 ASSERT_STREQ를 사용하면 된다. 특별한 경우, NULL을 비교해야 하는 경우가 생긴다면 ASSERT_STREQ(NULL, c_string)을 사용하면 되지만, 두 개의 문자열 object를 비교하려고 한다면 ASSERT_EQ를 사용한다.
ASSERT 구문은 narrow, wide string 모두에 대해 동작한다.
String Comparison
문자열 비교를 위해서는 다음의 assertion 구문들을 사용한다.
위의 assertion 구문중 CASE가 포함된 항목들이 있는데, CASE는 대소문자를 구별하지 않는다는걸 의미한다. 또 NULL pointer와 비어있는 문자열은 다름을 유의해야 한다.
Simple Tests
Test를 작성하기 위해 일단 아래의 절차대로 수행한다.
1. Test function을 정의하고 이름 붙이기 위해서 TEST() macro를 사용한다. 이 macro는 일반적은 C++ function으로 구성되어 있으며 결과값을 return 하지 않는다.
2. 이 function에서 사용자가 원하는 가능한 C++ 구문들을 사용하면 되고, 값의 비교를 위해서는 google test의 assertion 구문을 사용하면 된다.
3. Test 결과는 이 assertion 구문에 의해 결정된다.
TEST(test_case_name, test_name) {
... test body ...
}
TEST() macro에서 첫번째 argument는 testcase의 이름을 의미하며 두번째 argument는 testcase 안에서의 test 이름을 의미한다. Testcase는 여러개의 test를 포함할 수 있다. 또 다른 testcase에서 같은 test 이름을 가질 수 있다.
예를 들어보자. 간단한 function인
int Factorial(int n); // Returns the factorial of n
이 있다.
이 function을 test 하기 위해서 아래와 같은 testcase를 작성할 수 있다.
// 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));
}
Google test는 testcase에 의해 grouping 하는 것이 가능한데, TEST()의 첫번째 argument를 동일하게 사용함으로써 이를 가능케 만들 수 있다. 위의 예제에서 보면 HandlesZeroInput 이라는 test와 HandlesPositiveInput test는 각각 다른 test를 의미하지만 FactorialTest 라는 testcase의 이름을 동일하게 사용함으로써 grouping이 가능함을 보여주고 있다.
Text Fixtures : Using the Same Data Configuration for Multiple Tests
사용자가 유사한 data를 사용해서 하나 이상의 test를 작성한다면, test fixture를 사용할 수 있다. 이 test fixture를 사용한다는 것은 여러개의 다양한 test를 작성하는 과정에서 같은 object의 configuration을 재사용한다는 것을 의미한다.
Fixture를 작성할 때에는 아래의 내용대로 수행하면 된다.
1. ::testing::Test 로부터 class를 derive한다. Sub-class 에서 fixture member에 접근해야 하기 때문에 protected 혹은 public 으로 작성해야 한다.
2. Class 내부에서 사용자가 원하는대로 object들을 선언해 사용한다.
3. 필요하다면, 생성자나 SetUp() function을 작성해둔다.
4. 생성자나 SetUp() function을 정의해서 사용하고 있다면, 해당 function에서 사용했던 resource를 반환하기 위해 소멸자나 TearDown() function을 작성한다.
5. Subroutine 들을 작성한다.
Fixture를 사용하기 위해서는 TEST() 대신에 TEST_F()를 사용해야만 한다.
TEST()에서는 첫번째 argument가 testcase의 이름이었지만 TEST_F()를 사용할 때는 첫번째 argument로 test fixture class의 이름을 사용해야만 한다.
불행하게도 C++ macro 라는게 두가지 type을 처리하는 하나의 macro를 지원하지 않기 때문에 어쩔 수 없이 두 가지의 macro TEST(), TEST_F()를 적절히 사용해야만 한다. 또 당연하겠지만 TEST_F() macro를 사용하기 이전에 TEST_F()에 사용될 fixture class가 정의되어 있어야만 한다.
TEST_F()를 사용할 때, google test는 아래와 같은 절차를 수행하게 된다.
1. Runtime 시에 test fixture를 생성한다.
2. SetUp()을 호출해 초기화를 수행한다.
3. Test를 수행한다.
4. TearDown()을 호출함으로써 resource를 반환하거나 하는 처리를 한다.
5. Test fixture를 소멸시킨다. 주의할 내용은 같은 testcase 내부에서의 각각의 test는 다른 test fixture를 확보해 사용하며 google test는 다음 test fixture를 생성하기 전에 이전 fixture를 삭제한다. Google test는 여러개의 test를 위해 동일한 test fixture를 재사용하지 않으며 하나의 test가 test fixture에 가한 변화가 다른 test 들에는 영향을 주지 않음을 의미한다.
아래의 예제를 살펴보자. Queue라는 이름의 queue class에 대한 test를 작성한다고 생각해보자.
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;
...
};
가장 먼저 fixture class를 정의한다.
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_;
};
이 경우에 각 test 마다 소멸자가 대신 수행하기 때문에 깨끗하게 정리할 필요가 없어서 TestDown()을 작성하지 않았다. 이 작업까지 마친 후에 아래처럼 test fixture를 이용해 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;
}
이 test들이 수행될 때 아래와 같은 과정을 거치게 된다.
1. Google test가 QueueTest object를 생성한다. (t1이라 부르자.)
2. t1.SetUp() 이 호출되어서 t1을 초기화한다.
3. t1의 첫번째 test인 IsEmptyInitially가 수행된다.
4. Test 종료 후에 t1.TearDown()이 호출되어 정리한다.
5. t1이 소멸된다.
6. 다른 QueueTest object에 대해 위의 과정이 반복되는데, 이번엔 DequeueWork test에 대해 반복 수행된다.
Invoking the Tests
TEST()와 TEST_F() 는 google test에서 내부적으로 등록된다. 그렇기 때문에 다른 C++ testing framework 들과 달리 test를 수행하기 위해 다시 정의할 필요가 없다.
Test 들을 정의한 후에 RUN_ALL_TESTS()를 사용해서 test를 수행시킬 수 있다. RUN_ALL_TESTS()는 모든 test가 성공일 때에는 0을 반환하고 아닐 경우에는 1을 return하는 macro이다. RUN_ALL_TESTS()는 아래와 같은 과정을 거치면서 수행된다.
1. Google test의 모든 flag들의 상태를 저장한다.
2. 첫번째 test를 위한 test fixture를 생성한다.
3. SetUp()으로 초기화한다.
4. Fixture object와 함께 test를 수행한다.
5. TearDown()을 호출해서 resource들을 반환하거나 정리한다.
6. Fixture object를 제거한다.
7. Google test의 flag들을 복구한다.
8. 모든 test가 완료될 때 까지 위의 과정을 반복한다.
RUN_ALL_TEST()는 한 번 이상 수행하는걸 지원하지 않기 때문에 단 한번만 호출될 수 있도록 작성해야 한다.
Writing the main() function
아래의 예처럼 test에 필요한 test fixture나 main() function을 작성할 수 있다.
#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();
}
이 예제에서 main() function에 사용된 ::testing::InitGoogleTest() function은 google test의 flag를 설정하기 위해 command line으로 입력된 값들을 사용한다. 이런 방법은 사용자가 다양한 flag를 통해 test program을 제어 가능하도록 해준다.
Important note for Visual C++ users
만약 사용자가 test를 library에 넣어두었고, main() function이 .exe 파일 안에서 별도의 library로 작성되어 있다면 test는 수행되지 않을것이다. 이유는 Visual C++의 bug 때문인데, 사용자가 test를 정의했을 때, google test는 test를 등록하기 위해 static object로 생성한다. 이러한 object들은 어디서든 참조되지 못하지만 생성자들은 여전히 수행하려고 한다. 그래서 사용자는 main program에서 test와 함께 library를 참조해야만 한다. 그러기 위해서는 library에서 아래처럼 function을 선언한다.
__declspec(dllimport) int PullInMyLibrary() { return 0; }
DLL이 아닌 static library로 작성했다면 위와 같은 선언은 필요하지 않지만, main program에서는 아래와 같이 작성해 주어야 한다.
int PullInMyLibrary();
static int dummy = PullInMyLibrary();
추가로 static library로 test를 작성한다면 main program의 linker option에 /OPT:NOREF를 넣어주어야 하고, test를 DLL로 작성하게 된다면 google test 역시 DLL로 build 해주어야 test가 제대로 수행될 수 있다. 그래도 가장 손쉬운 방법은 test를 library로 만들지 않는 것이다.