C++ GoogleTest 사용하여 유닛테스트 작성하기 (UnitTest)
31 Mar 2020 | c++ programming effectiveWhy GoogleTest?
구글에서 만든 C++ Testing Framework.
1. 테스트는 독립적이고 반복할 수 있어야 한다. 다른 테스트의 결과에 따라 결과가 달라지는 테스트는 좋은 테스트가 아니다. 구글테스느는 각각의 테스트를 분리하여 다른 오브젝트로 관리할 수 있도록 도와준다.
2. 테스트는 잘 구조화되고 테스트하는 코드를 잘 반영해야 한다. 구글테스트는 관련된 테스트를 test suite로 그룹화하여 데이터와 subroutine을 공유할 수 있도록 한다.
3. 테스트는 재사용 가능하고 플랫폼 종속되지 않아야 한다. 구글테스트는 다른 OS에서도 돌 수 있도록 한다.
4. 테스트가 Fail했을 때 왜 실패했는지를 보고해주기 때문에 버그를 쉽게 찾을 수 있다.
5. 테스팅 프레임워크는 테스트 작성자들의 귀찮음을 덜어주고 테스트 자체에 집중할 수 있도록 만들어준다.
6. 테스트는 빨라야한다. 구글테스트를 이용해서 shared resource를 테스트간에 재사용 할 수 있고, 한번만 실행되는 set-up/tear-down 메소드도 사용할 수 있다.
구글테스트는 xUnit 아키텍쳐에 기반하고 있기 때문에 만약 JUnit이나 PyUnit과 익숙하다면 구글테스트에 빨리 익숙해질 수 있을 것이다.
환경: Ubuntu 16.04
GoogleTest Build
$ git clone https://github.com/google/googletest $ cd googletest $ mkdir build $ cd build $ cmake .. $ make $ make install
Assertion
Fatal assertion (ASSERT_) 는 테스트 실패시 바로 테스트가 중단된다.
Nonfatal assertion (EXPECT_) 는 테스트 실패해도 모든 테스트를 실행한다.
보통은 EXPECT_를 쓰나, 이 테스트가 실해하면 무조건 바로 중단해야 할 경우는 ASSERT_를 쓴다.
Basic Assertion
Fatal assertion | Nonfatal assertion | Verifies ————————– | ————————– | ——————– ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition is true ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition is false
Binary Comparison
Fatal assertion | Nonfatal assertion | Verifies
———————— | ———————— | ————–
ASSERT_EQ(val1, val2);
| EXPECT_EQ(val1, val2);
| val1 == val2
ASSERT_NE(val1, val2);
| EXPECT_NE(val1, val2);
| val1 != val2
ASSERT_LT(val1, val2);
| EXPECT_LT(val1, val2);
| val1 < val2
ASSERT_LE(val1, val2);
| EXPECT_LE(val1, val2);
| val1 <= val2
ASSERT_GT(val1, val2);
| EXPECT_GT(val1, val2);
| val1 > val2
ASSERT_GE(val1, val2);
| EXPECT_GE(val1, val2);
| val1 >= val2
String Comparison
| Fatal assertion | Nonfatal assertion | Verifies |
| ————————– | —————————— | ——————————————————– |
| ASSERT_STREQ(str1,str2);
| EXPECT_STREQ(str1,str2);
| the two C strings have the same content |
| ASSERT_STRNE(str1,str2);
| EXPECT_STRNE(str1,str2);
| the two C strings have different contents |
| ASSERT_STRCASEEQ(str1,str2);
| EXPECT_STRCASEEQ(str1,str2);
| the two C strings have the same content, ignoring case |
| ASSERT_STRCASENE(str1,str2);
| EXPECT_STRCASENE(str1,str2);
| the two C strings have different contents, ignoring case |
Simple Test
TEST() 라는 매크로를 쓴다. 이것은 결과를 리턴하지 않는 평범한 C++ 함수이다.
첫번째 파라미터는 test suite의 이름이고, 두번째 파라미터는 구체적인 테스트의 이름이다.
이름에는 underscores (_)가 들어갈 수 없다.
TEST(TestSuiteName, TestName) { ... test body ... }
Example
// Tests factorial of 0. TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(Factorial(0), 1); } // Tests factorial of positive numbers. TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(Factorial(1), 1); EXPECT_EQ(Factorial(2), 2); EXPECT_EQ(Factorial(3), 6); EXPECT_EQ(Factorial(8), 40320); }
Test Fixtures (Setup()/TearDown())
같은 데이터 설정을 여러 테스트에서 사용하고 싶을 때 필요한 방법이다.
::testing::Test를 상속받는다. Setup()과 같은 메소드들은 protected로 정의한다.
또한 TEST() 대신 TEST_F()를 사용한다.
TEST_F(TestFixtureName, TestName) { ... test body ... }
Example
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; ... };
class QueueTest : public ::testing::Test { protected: void SetUp() override { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } // void TearDown() override {} Queue<int> q0_; Queue<int> q1_; Queue<int> q2_; };
TEST_F(QueueTest, IsEmptyInitially) { EXPECT_EQ(q0_.size(), 0); } TEST_F(QueueTest, DequeueWorks) { int* n = q0_.Dequeue(); EXPECT_EQ(n, nullptr); n = q1_.Dequeue(); ASSERT_NE(n, nullptr); EXPECT_EQ(*n, 1); EXPECT_EQ(q1_.size(), 0); delete n; n = q2_.Dequeue(); ASSERT_NE(n, nullptr); EXPECT_EQ(*n, 2); EXPECT_EQ(q2_.size(), 1); delete n; }
main function
gtest_main과 Link했다면 메인함수를 작성할 필요는 없다. gtest_main을 링크한다면 구글 테스트가 메인 함수 기본 구현을 제공하기 때문이다. 만약 자신만의 메인을 만들고 싶다면 RUN_ALL_TEST()를 리턴하게 해야 한다.
#include "this/package/foo.h" #include "gtest/gtest.h" namespace my { namespace project { namespace { // The fixture for testing class Foo. class FooTest : public ::testing::Test { protected: // You can remove any or all of the following functions if their bodies would // be empty. FooTest() { // You can do set-up work for each test here. } ~FooTest() override { // 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: void SetUp() override { // Code here will be called immediately after the constructor (right // before each test). } void TearDown() override { // Code here will be called immediately after each test (right // before the destructor). } // Class members declared here can be used by all tests in the test suite // for Foo. }; // Tests that the Foo::Bar() method does Abc. TEST_F(FooTest, MethodBarDoesAbc) { const std::string input_filepath = "this/package/testdata/myinputfile.dat"; const std::string output_filepath = "this/package/testdata/myoutputfile.dat"; Foo f; EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0); } // Tests that Foo does Xyz. TEST_F(FooTest, DoesXyz) { // Exercises the Xyz feature of Foo. } } // namespace } // namespace project } // namespace my int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
만약 더 공부해보고 싶다면 Advanced Guide 을 참고한다.