들어가기 앞서…
- 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