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 을 참고한다.