Dev

Google Mock 사용을 위한 간단한 정리

prostars 2017. 8. 10. 21:10

앞서 포스팅했던 단위 테스트 관련 글들에 이어서 이번에는 C++ 에서 단위 테스트를 사용하기 위한 프레임웍으로 Google Test와 Google Mock을 간단히 소개하겠다.


Google Test : https://github.com/google/googletest

Google Mock : https://github.com/google/googletest/tree/master/googlemock


세부적인 Assertions 함수들은 아래 문서를 참조하자.

https://github.com/google/googletest/blob/master/googletest/docs/Primer.md



여기서는 Mocking 을 어떻게 구성하는지 간단한 Sample Project 를 가지고 설명한다.

https://github.com/prostars/SampleForGoogleMock


이 글을 읽는 여러분이 C++ 에 익숙하다고 믿고 큰 줄기만 정리하고 넘어가겠다.

나머지는 코드를 직접 확인하기 바라며 이 글을 읽는 동안 코드를 열어놓고 같이 보는 것을 권장한다.


Visual Studio 2013을 기준으로 Sample Project 구성하는 방법을 같이 보자.

Win32 콘솔 프로젝트를 하나 생성한다.

정적라이브러리로 설정하고 추가 옵션은 모두 해제한다.

솔루션 폴더에 3rdLib 빈 폴더를 하나 추가한다.


https://github.com/google/googletest 에서 프로젝트를 zip으로 다운로드 받는다.

받아서 압축을 풀고 위에서 생성한 3rdLib 폴더에 googletest, googlemock 두 폴더를 복사한다.


위 솔루션을 열고 프로젝트를 추가하는데 기존 프로젝트로 선택하고 googletest/msvc/gtest.vcproj 를 선택한다.

단방향 업그레이드 팝업이 열릴 텐데 진행한다. 보안 경고가 열릴 텐데 무시하고 진행한다.

빌드를 해본다. 잘 될 것이다. 에러가 발생했다면 건투를 빈다.


다시 프로젝트를 추가하는데 이번에도 기존 프로젝트를 선택하고 googlemock/msvc/2010/gmock.vcxproj 를 선택한다. 이번에도 보안 경고를 무시한다.

이번에는 버전 문제로 빌드에 바로 실패할 것이다. 

방금 추가된 gmock(Visual Studio 2010) 프로젝트에서 마우스 우클릭하여 팝업 메뉴를 열고 VC++ 컴파일러 및 라이브러리 업그레이드(R) 를 선택하고 계속 진행한다.

다시 빌드를 시도하면 잘 될 것이다.


단위 테스트를 담당할 Win32 콘솔 프로젝트를 하나 더 추가하자.
프로젝트 이름은 testsuite로 하고 콘솔 프로젝트 설정은 유지하고 이번에도 추가 옵션은 모두 해제한다.
calculator에 대한 빌드 종속성을 추가한다.
모든 프로젝트의 코드 생성 설정에서 다중 스레드 디버그(/MTd)로 설정한다.

이제 기본적인 환경 설정은 다 했다.

샘플 프로젝트를 받아서 전체 코드를 확인할 수 있다.

샘플 프로젝트의 기본적인 구성은 다음과 같다.


Calcurator 는 AccountStorage 에서 정보를 가지고 와서 총합과 평균을 구할 수 있는 기능을 가진다.

AccountStorage 는 월별 매출 정보가 저장되어있는 DB에 접근할 수 있는 Interface 다.

단위 테스트가 DB에 종속성을 가지는 것은 아름답지 않으므로 Mocking 해야 할 대상이다.


AccountStorage 를 상속 받아서 FakeAccountStorage 구현체를 만든다.
테스트용 데이터를 제공할 간이 메모리 DB 다.

테스트 케이스에서 실제로 사용할 MockAccountStorage 도 AccountStorage 를 상속 받아서 만든다.
MockAccountStorage 에서 사용하는 MOCK_METHOD2 와 같은 MOCK_METHOD(n) 매크로 함수 시리즈는 가장 간단한 Mocking 방법을 제공한다. 

MOCK_METHOD2(getValue, bool(int month, int& value)); 
위와 같은 코드는 getValue() 메소드에 대한 빈 구현체를 제공하며 호출되었을 때 Google Mock 에서 제공하는 다른 기능들과 연계할 수 있도록 처리해준다. 여기서는 EXPECT_CALL() 과 ON_CALL() 에서 사용할 것이다.

MockAccountStorage  에서 MOCK_METHOD(n) 매크로 함수와 같이 사용하는 ON_CALL() 매크로 함수는 MOCK_METHOD(n) 으로 설정된 메소드가 호출되었을 때에 대한 처리를 지정할 수가 있다.

여기서는 호출된 Mock 함수에 대응하는 Fake 메소드를 호출하도록 지정했다.

코드는 아래와 같다.


ON_CALL(*this, getValue(_, _)).WillByDefault(::testing::Invoke(&fake, &FakeAccountStorage::getValue));

getValue() 메소드가 어떤 파라미터 값으로 호출이 되던지 상관없이 FakeAccountStorage::getValue() 를 호출하도록 지정한 것이다.

위 매크로 함수들의 세부 기능 설명은 Google Mock 문서에서 찾을 수 있다.


자 이제 간단한 테스트 시나리오를 준비하자.
총합을 구하는 시나리오와 평균을 구하는 시나리오 2개만 하겠다.

각 시나리오 모두 몇 개월 치의 데이터가 필요하고 공통으로 사용할 것이므로 Test Fixture 기능으로 처리하자.

::testing::Test 를 상속받아서 FixtureTestCalculator 를 구현한다.
SetUp() 메소드는 테스트 시작 전에 호출되어 테스트에 필요한 준비를 진행한다.
TearDown() 메소드는 테스트 종료 후에 호출되어 테스트에 사용된 내용을 정리한다.

class FixtureTestCalculator : public ::testing::Test {

protected:

void SetUp() override {

storage = std::make_shared<MockAccountStorage>();

 

storage->fake.insertValue(1, 100);

storage->fake.insertValue(2, 300);

storage->fake.insertValue(3, 200);

storage->fake.insertValue(4, 600);

storage->fake.insertValue(5, 40);

}

 

void TearDown() override {

storage.reset();

}

 

public:

std::shared_ptr<MockAccountStorage> storage;

};


여기서는 SetUp() 에서 MockAccountStorage 에 5개월 치의 정보를 등록하고 TearDown() 에서 MockAccountStorage 를 해제한다.


아래 코드는 위에서 이야기한 2개의 테스트 케이스에 대한 코드다.


TEST_F(FixtureTestCalculator, SuccessToSum) {

FEATURE("Calculator storage 에서 월별 매출에 대한 총합을 구할 수 있다.");

SCENARIO("storage 저장된 월별 매출에 대한 총합을 구한다.");

 

GIVEN("1 부터 5월까지 5개월에 대한 매출 정보가 있다.");

Calculator calculator(this->storage);

 

WHEN("총합을 구했을 때");

 

THEN("모든 정보를 1번만 로딩해야 하고,");

EXPECT_CALL(*(this->storage), getMonths(_)).Times(1);

int sum = 0;

calculator.sum(sum);

 

AND("총합은 1240 이어야 한다.");

EXPECT_EQ(sum, 1240);

}

 

TEST_F(FixtureTestCalculator, SuccessToAverage) {

FEATURE("Calculator storage 에서 월별 매출에 대한 평균을 구할 수 있다.");

SCENARIO("storage 저장된 월별 매출에 대한 평균을 구한다.");

 

GIVEN("1 부터 5월까지 5개월에 대한 매출 정보가 있다.");

Calculator calculator(this->storage);

 

WHEN("평균을 구했을 때");

 

THEN("모든 정보를 2번 로딩해야 하고,");

EXPECT_CALL(*(this->storage), getMonths(_)).Times(2);

double average = 0;

calculator.average(average);

 

AND("248 이어야 한다.");

EXPECT_EQ(average, 248);

}

TEST_F() 매크로 함수는 단순한 TEST() 매크로 함수와 달리 첫번째 파라미터로 Test Fixture 를 지정할 수 있는 기능을 제공한다. 두번째 파라미터는 그냥 테스트 케이스의 제목이다.


위 코드에서 보이는 GIVEN(), WHEN(), THEN() 등의 함수는 Google Test 에서 제공하는 함수가 아니고 BDD 스타일을 사용하기 위해 간단히 구현한 console print 함수들이고 TestUtil.h 에 구현되어 있다.

위에서 Google Test 와 Google Mock 에서 제공하는 기능은 EXPECT_CALL() 과 EXPECT_EQ() 다.

EXPECT_EQ() 는 지정된 파라미터 2개가 서로 같지 않으면 테스트 케이스를 실패로 처리한다.

EXPECT_CALL() 는 지정된 함수가 지정된 조건으로 호출되지 않으면 테스트 케이스를 실패로 처리한다.

여기서는 getMonths() 메소드가 총합 케이스에서는 1번, 평균 케이스에서는 2번 호출되어야 한다고 지정했다.


더 부연 설명할 만한 내용이 없는 간단한 Sample 이므로 나머지는 직접 가지고 놀아보기 바란다.

예를 들어 ON_CALL 지정을 주석 처리하고 테스트를 실행하면 테스트 케이스는 실패할 것이다.


최대한 단순하게 Mocking 처리하는 한 사이클을 정리하는 것이 목적이었으므로 여기서 마무리하겠다.

다음에는 비동기 단위 테스트에 대해서 정리하겠다.


반응형