일하는

How To Use Google Mock (gmock)

김논리 2020. 8. 28. 14:44

Google Test (gtest)에서 제공하는 gmock의 기본적인 사용법에 대해 설명한다.

본 문서는 Ubuntu 18.04 환경 기준으로 작성되었다. (gtest 라이브러리가 설치되어 있다는 가정 하에 작성)

gmock library와 관련된 자세한 내용은 🏠   https://github.com/google/googletest 에서 확인할 수 있다.


우리가 만드는 Target 이라는 프로그램이 ExternalInterface 를 이용하여 서버나 데이터베이스, 또는 다른 외부 라이브러리를 이용한다고 가정해 보자.

 

ExternalInterface 는 다음과 같이 3가지 기능을 제공한다.

 

// 외부 dependency를 갖고 있는 인터페이스
// server connection 이나 system, DB 관련 기능을 사용하는 경우 등이 있다.

class ExternalInterface {
public:
    // 가상의 ExternalInterface 에서 아래와 같이 3가지 기능을 제공한다고 생각해 보자.
    virtual void init() { std::cout << __FUNCTION__ << std::endl; }
    virtual int getInt(std::string prarm) = 0;
    virtual std::string getString(int param) = 0;
}

 

Target 의 일부 함수에서는 ExternalInterface 를 이용하여 이 3가지 기능을 사용한다고 가장하자.

 

// Unit Test를 수행하고자 하는 대상 Class
// 일부 함수에서 외부 인터페이스를 갖는 ExternalInterface Class를 사용한다고 가정하자.

class Target {
public:
    Target(std::shared_ptr<ExternalInterface< ei) { this->ei = ei; }
    ~virtual Target() {}
    
    // ExternalInterface 클래스를 사용하지 않는 멤버 함수
    int testFunction1() { std::cout << __FUNCTION__ << std::endl; return 1; }
    int testFunction2() { std::cout << __FUNCTION__ << std::endl; return 2; }
    
    // ExternalInterface 클래스를 사용하는 멤버 함수
    void testFunction3() {
        std::cout << __FUNCTION__ << std::endl;
        ei->init();
    }
    
    int testFunction4(std::string param) {
        std::cout << __FUNCTION__ << std::endl;
        return ei->getInt(param);
    }
    
    std::string testFunction5(int param) { 
        std::cout << __FUNCTION__ << std::endl;
        std::cout << ei->getString(param) << std::endl;
        return ei->getString(param);
    }

protected:
    std::shared_ptr<ExternalInterface> ei;
};

 

Target 클래스의 Unit test를 작성하는 경우, ExternalInterface 에 대한 의존성을 끊어야만 제대로된 테스트가 가능할 것이다.

(예를 들어, 서버 연동 기능의 경우 네트워크 연결 유무나 서버의 장애 유무와 관계 없이 Unit test 가 수행되어야 한다.)

 

이런 경우, Google Mock (gmock) 을 이용하여 간단하게 ExternalInterface 클래스를 대체하는 가짜 클래스를 만들어 낼 수 있다.

ExternalInterfaceMock 클래스를 아래와 같이 만든다.

 

// 인터페이스 클래스의 Mock을 만든다.

class ExternalInterfaceMock : public ExternalInterface {
public:
    // Mock 처리를 원하는 함수를 지정한다.
    // Init 함수를 제외한 getString, getInt 함수만 Mock 처리를 한다고 가정하자.
    
    // param의 개수에 따라 MOCK_METHOD{N} 매크로를 사용해야 한다.
    //             함수명         함수 시그니처
    MOCK_METHOD1(getString, std::string(int));
    MOCK_METHOD2(getInt,    int(std::string));
}

 

 

이제 Unit Test 코드에서 ExternalInterface 클래스 대신, 앞서 만든 ExternalInterfaceMock 클래스를 사용하도록 수정해 보자.

 

class TargetTest : public Test {
protected:
    shared_ptr<Target> obj = nullptr;
    shared_ptr<ExternalInterfaceMock> mock = nullptr;
    
    virtual void SetUp()
    {
        // Mock 객체를 만든다
        mock = make_shared<ExternalInterfaceMock>();
        
        // 새로 만드느 Mock 객체를 이용하여 Target 객체를 생성한다.
        obj = make_shared<Target>(mock);
    }
    
    virtual void TearDown() {}
};

// ExternalInterface 클래스를 사용하지 않는 멤버 함수 테스트
TEST_F(TargetTest, testFunction1)
{
    EXPECT_EQ(1, obj->testFunction1());
}

TEST_F(TargetTest, testFunction2)
{
    EXPECT_EQ(2, obj->testFunction2());
}

// ExternalInterface 클래스를 사용하는 멤버 함수 테스트
// Mocking하지 않은 함수를 테스트 하는 경우
TEST_F(TargetTest, testFunction3)
{
    // 이 경우, 실제 ExternalInterface의 init() 함수가 호출된다.
    obj->testFunction3();
}

// Mocking한 함수를 테스트 하는 경우
// --> ExternalInterfaceMock 클래스가 사용되어야 한다.
TEST_F(TargetTest, testFunction4)
{
    // testFunction4 에서 사용하는 ei->getInt는 int를 리턴하므로, 
    // Mock 객체에 호출 횟수와 함께 리턴값을 설정해 보자. (parameter 체크도 함께 할 수 있다.)
    EXPECT_CALL(*mock, getInt("test param"))
        .Times(::testing::AtLeast(1))
        .WillOnce(::testing::Return(4));
    
    // 실제 Target 함수를 수행한다.
    EXPECT_EQ(4, obj->testFunction4("test param"));
    
    // 이 케이스의 경우, 해당 함수가 "test param" 이란 parameter를 가지고 적어도 1번 호출되었는지 확인하며,
    // 설정한 "4"를 한번 리턴하게 된다.
}

TEST_F(TargetTest, testFunction5)
{
    // testFunction5 에서 사용하는 ei->getString은 2번 호출되고 string을 리턴하므로,
    // Mock 객체에 호출 횟수와 함께 리턴값을 설정해 보자.
    EXPECT_CALL(*mock, getString(5))
        .Times(2)
        .WillRepeatedly(::testing::Return("test return"));
    
    // 실제 Target 함수를 수행한다.
    EXPECT_EQ("test return", obj->testFunction5(5));
    
    // 이 케이스의 경우, 해당 함수가 "5" 란 parameter를 가지고 2번 호출되었는지 확인하며,
    // 설정한 "rest return"을 반복적으로 리턴하게 된다.
}

 

위 코드를 사용하여 만들어진 Unit test binary를 실행하면, 다음과 같은 결과를 얻을 수 있다.

 

$ ./gmock_sample
Running main() from gmock_main.cc
[==========] Running 5 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 5 tests from TargetTest
[ RUN      ] TargetTest.testFunction1
testFunction1
[       OK ] TargetTest.testFunction1 (0 ms)
[ RUN      ] TargetTest.testFunction2
testFunction2
[       OK ] TargetTest.testFunction2 (0 ms)
[ RUN      ] TargetTest.testFunction3
testFunction3
init
[       OK ] TargetTest.testFunction3 (0 ms)
[ RUN      ] TargetTest.testFunction4
testFunction4
[       OK ] TargetTest.testFunction4 (1 ms)
[ RUN      ] TargetTest.testFunction5
testFunction5
test return
[       OK ] TargetTest.testFunction5 (0 ms)
[----------] 5 tests from TargetTest (1 ms total)

[----------] Global test environment tear-down
[==========] 5 tests from 1 test case ran. (1 ms total)
[  PASSED  ] 5 tests.

 

'일하는' 카테고리의 다른 글

How To Use Google Logging Library (glog)  (0) 2020.08.28