Skip to content

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#

  1. Assertion : 조건이 참인지 체크하는 문장 Return 값 → success / nonfatal failure / fatal failure fatal failure 일때 abort
    EXPECT_EQ(fac(0), 0);
    
  2. Test : Assertion을 정의하기 위한 Case
    TEST(factorial_test, ZeroInput){
        EXPECT_EQ(fac(0), 0);
    }
    
  3. 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);
    }
    

  4. Test program : 1개 이상의 Test Suite 그룹 ⇒ test.cc

Test P → Test suite → Test → Assertion#

TEST process

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

Reference#