Google Test
들어가기 앞서...#
- GoogleTest ⇒ Google의 C++ 유닛 테스트 프레임워크
1. GoogleTest가 쓰이는 오픈소스 프로젝트#
- Chromium project (크롬 브라우저와 크롬 OS)
- OpenCV
- Android Open Source project
- etc..
2. Google Test를 사용하는 이유#
EX) 여러가지 자료구조를 설계해야 하는 상황
//Linked List
struct node{
int num;
struct node *next;
};
typedef struct node NODE;
typedef NODE* LINK;
// InsertNode : front - end
void InsertNode(LINK front, LINK end){
front->next = end;
}
// DeleteNode : Delete All LINK in target Node
void Deletenode(LINK node){
free(node->next);
}
// PrintNode : print All element in each node
void PrintNode(LINK node){
while(!node->next)
printf("%d ", node->num);
puts("");
}
Single Linked List 미완성 코드에서 - InsertNode 함수 - front, end == NULL인 상황 고려 X - DeleteNode 함수 - 타겟 노드가 3개 이상 NODE를 가진 상황에서 free 했을 때 메모리 누수 발생 가능성 O
등 고려하지 않은 상황이 많으며 아직 해당 코드가 올바르게 동작하는 지 테스트를 해볼 필요가 있다.
Main 문에서 테스트#
void main(){
LINK test;
test->num = 1;
test->LINK = NULL;
PrintNode(test);
}
- 프로그램이 커질수록 테스팅 비용이 증가한다
- CreateNode() 새로운 함수 추가 : PrintNode() → InsertNode() → DeleteNode() 정상 동작 확인
Unit Test의 도입#
- 문제점 발견이 용이
- 프로그램을 나눠서 테스트 ex) Linked List 관련 코드 + Queue 관련 코드
- 상황을 나눠서 테스트 ex) 노드가 1개일 때 상황 / 2개일 때 / 3개일 때 ...
- 변경이 쉬움
- 코드 리팩토링 용이 ⇒ 검증하고자 하는 부분(메소드 등등)만 빼내서 테스트 가능
- Regression Test ⇒ CreateNode 함수 변경시 InsertNode 함수에 영향 가는지 테스트
- 통합이 간단
- 유닛 테스트 → 통합 테스트 ex) 위 예시에서 DeleteNode 함수 메모리 해제 잘 했는 지 테스트
- 코드 재사용시 용이 ex) Linked List를 통해 Tree 구현
3. Google Test의 특징#
- 테스트와 객체 분리 : 한 테스트로 여러 객체 테스트 가능
- 다양한 OS 및 컴파일러 지원 : Linux / Windows / Mac
- pthread 라이브러리를 이용하기 때문에 Thread-Safe 보장 받음
4. Terminology#
- Assertion : 조건이 참인지 체크하는 문장
Return 값 → success / nonfatal failure / fatal failure
fatal failure 일때 abort
EXPECT_EQ(fac(0), 0);
- Test : Assertion을 정의하기 위한 Case
TEST(factorial_test, ZeroInput){ EXPECT_EQ(fac(0), 0); }
-
Test suite : 1개 이상의 Test 그룹 그룹 내의 Test들이 공유 자원을 사용할 경우 test fixture class 이용
TEST(factorial_test, ZeroInput){ EXPECT_EQ(fac(0), 0); } TEST(factorial_test, PosInput){ EXPECT_EQ(fac(1), 1); EXPECT_EQ(fac(2), 2); EXPECT_EQ(fac(3), 6); EXPECT_EQ(fac(4), 24); EXPECT_EQ(fac(5), 120); }
-
Test program : 1개 이상의 Test Suite 그룹 ⇒
test.cc
Test P → Test suite → Test → Assertion#
5. Google Test 예시#
구성#
- fac.h
- fac.cc
- test.cc
- Makefile
Test P | Test Suite | Test | Assertion |
---|---|---|---|
test.cc | factorial_test | ZeroInput | EXPECT_EQ |
PosInput | EXPECT_EQ | ||
fibonacci_test | ZeroInput | EXPECT_EQ | |
PosInput | EXPECT_EQ |
코드#
// fac.h
int fac(int n);
// fac.cc
#include "fac.h"
int fac(int n){
if(n<1) return 1;
else return(n * fac(n-1));
}
// test.cc
#include <stdio.h>
#include <gtest/gtest.h>
#include "fac.h"
TEST(factorial_test, ZeroInput){
EXPECT_EQ(fac(0), 0);
}
TEST(factorial_test, PosInput){
EXPECT_EQ(fac(1), 1);
EXPECT_EQ(fac(2), 2);
EXPECT_EQ(fac(3), 6);
EXPECT_EQ(fac(4), 24);
EXPECT_EQ(fac(5), 120);
}
int main(int argc, char **argv){
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
// Makefile
.PHONY: test
GTEST_DIR=googletest/googletest
test:
g++ -o test fac.cc test.cc -isystem ${GTEST_DIR}/include -L${GTEST_DIR}/build -pthread -lgtest
./test
결과#
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from factorial_test
[ RUN ] factorial_test.ZeroInput
[test.cc:8](http://test.cc:8/): Failure
Expected equality of these values:
fac(0)
Which is: 1
0
[ FAILED ] factorial_test.ZeroInput (0 ms)
[ RUN ] factorial_test.PosInput
[ OK ] factorial_test.PosInput (0 ms)
[----------] 2 tests from factorial_test (0 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
[ FAILED ] 1 test, listed below:
[ FAILED ] factorial_test.ZeroInput
1 FAILED TEST
make: *** [Makefile:6: test] Error 1
6. 테스트 프로그램 작성법#
// test.cc
#include <stdio.h>
#include <gtest/gtest.h> // -> gtest/gtest 참조
#include "fac.h"
//TEST(test_case_name, test_name)
TEST(factorial_test, ZeroInput){
EXPECT_EQ(fac(0), 0);
}
TEST(factorial_test, PosInput){
EXPECT_EQ(fac(1), 1);
EXPECT_EQ(fac(2), 2);
EXPECT_EQ(fac(3), 6);
EXPECT_EQ(fac(4), 24);
EXPECT_EQ(fac(5), 120);
}
int main(int argc, char **argv){
::testing::InitGoogleTest(&argc, argv); // -> InitGoogleTest 메소드 필요
return RUN_ALL_TESTS();
}
::testing::InitGoogleTest(&argc, argv)
: Command Line의 flag 파싱RUN_ALL_TESTS
: gTest 결과 값 반환 (1) gTest flag 저장 (2) fixture 객체 생성 (3) Setup 이후 테스트 (4) fixture 객체 삭제 (5) gTest flag 복원 (6) 반복
7. Test Fixtures#
공유 자원을 다양한 테스트에서 활용해야할 경우 사용
Fixture Form#
TEST_F(TestFixtureName, TestName) {
... test body ...
}
Queue Type#
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;
...
};
test fixture#
::testing::Test
상속 받아서 사용- Constructor 나 SetUp() 함수 필요
- Deconstructor TearDown() 통해 Release
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 Code#
namespace{
...
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;
}
...
}
int main(int argc, char **argv){
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
결과#
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from QueueTestSmpl3
[ RUN ] QueueTestSmpl3.DefaultConstructor
[ OK ] QueueTestSmpl3.DefaultConstructor (0 ms)
[ RUN ] QueueTestSmpl3.Dequeue
[ OK ] QueueTestSmpl3.Dequeue (0 ms)
[ RUN ] QueueTestSmpl3.Map
[ OK ] QueueTestSmpl3.Map (0 ms)
[----------] 3 tests from QueueTestSmpl3 (0 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 3 tests.
8. Assertion#
- FAIL()
/ ADD_FAILURE()
#
switch(expression) {
case 1:
... some checks ...
case 2:
... some other checks ...
case 3:
std::cout << ADD_FAILURE(); // fatal failure
default:
std::cout << FAIL(); // non-fatal failure
}
8-1. Condition Test#
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition is true |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition is false |
8-2. Binary Comparison#
Compare Two Value
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 |
8-3. String Comparison#
Compare Two String
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 |
9. Linked List 설계#
Linked List Class#
class LL{
public:
int data_;
LL* next_;
LL() : next_(nullptr) {}
LL* InsertNode(int data){
LL *NODE = new LL();
NODE->data_ = data;
NODE->next_ = nullptr;
return NODE;
}
};
test code#
#include "gtest/gtest.h"
#include "custom_list.h"
namespace{
class SimpleTest : public ::testing::Test{
protected:
void SetUp() override{
LL1 = new LL();
}
// TearDown
LL* LL1;
};
TEST_F(SimpleTest, InsertNodeTest){
LL1->InsertNode(1);
EXPECT_TRUE(LL1 != nullptr);
EXPECT_TRUE(LL1->next_ == nullptr);
ASSERT_EQ(LL1->data_, 1);
}
}
int main(int argc, char **argv){
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Makefile#
PHONY: test
GTEST_DIR=googletest/googletest
test:
g++ -o test test.cpp -isystem ${GTEST_DIR}/include -L${GTEST_DIR}/build -pthread -lgtest
./test
Output1#
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from SimpleTest
[ RUN ] SimpleTest.InsertNodeTest
test.cpp:21: Failure
Expected equality of these values:
LL1->data_
Which is: 0
1
[ FAILED ] SimpleTest.InsertNodeTest (0 ms)
[----------] 1 test from SimpleTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] SimpleTest.InsertNodeTest
1 FAILED TEST
LL1->InsertNode(1);
=>LL1 = LL1->InsertNode(1);
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from SimpleTest
[ RUN ] SimpleTest.InsertNodeTest
[ OK ] SimpleTest.InsertNodeTest (0 ms)
[----------] 1 test from SimpleTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test