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 |
---|