class Turtle {
...
virtual void PenUp() = 0;
virtual void PenDown() = 0;
virtual void Forward(int distance) = 0;
virtual void Turn(int degrees) = 0;
virtual void GoTo(int x, int y) = 0;
virtual int GetX() const = 0;
virtual int GetY() const = 0;
};
#include "gmock/gmock.h"
class MockTurtle : public Turtle {
public:
...
MOCK_METHOD(void, PenUp, (), (override));
MOCK_METHOD(void, PenDown, (), (override));
MOCK_METHOD(void, Forward, (int distance), (override));
MOCK_METHOD(void, Turn, (int degrees), (override));
MOCK_METHOD(void, GoTo, (int x, int y), (override));
MOCK_METHOD(int, GetX, (), (const, override));
MOCK_METHOD(int, GetY, (), (const, override));
};
class MyMock {
public:
MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));
};
#1 : gmock 이름을 testing 네임스페이스 포함
#2 : mock 객체 생성
#3 : 예상 동작 정의(이 메소드가 얼마나 호출 될 것인가? 어떤 인자와 함께? 어떤 동작을 할까? etc...).
#4 : 테스트 코드 실행, 부가적으로 결과값을 googletest assertions으로 확인. mock 객체의 메소드가 예상한것 보다 한번더 호출 되거나 예상하지 못한 인자로 메소드가 호출 되었는지 검사(실행중에 예상 동작과 다르게 동작하면 바로 테스트 실패)
#5 : mock 개체가 소멸될때 gmock은 자동으로 모든 예상 동작이 만족하는지 확인. 예상했던 함수가 예상했던 횟수만큼 호출 되었는지 검사(함수가 끝나서 mock 객체가 소멸할때까지 예상 했던 동작을 수행하지 않으면 테스트 실패).
#include "path/to/mock-turtle.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
// #1 : AtLeast 사용하기 위해 namespace 포함
using ::testing::AtLeast;
TEST(PainterTest, CanDrawSomething) {
// #2 : tutle_mock 객체 생성
MockTurtle tutle_mock;
// #3 : tutle_mock 객체에 예상 동작 설정
// tutle_mock 객체는 이 테스트에서 PenDown를 최소 한번 호출 할 것이다
EXPECT_CALL(tutle_mock, PenDown()).Times(AtLeast(1));
// 테스트 진행
// #4 : tutle_mock 객체를 이용하여 테스트 코드 실행,
// tutle_mock 객체가 PenDown 말고 다른 메소드 호출 하거나
PenDown를 예상하지 못한 인자로 호출하면 바로 테스트 실패
// #5 : TEST 메서드가 종료되어 tutle_mock이 소멸될때까지 tutle_mock이 PenDown을 호출하지 않으면 테스트 실패
Painter painter(&tutle_mock);
EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}
ON_CALL(factory, DoMakeTurtle) // factory 객체의 DoMakeTurtle가 호출될때
.WillByDefault(Return(MakeMockTurtle()));// MakeMockTurtle() 반환
EXPECT_CALL(turtle, GetX()) // turtle 객체의 GetX() 호출될때
.Times(5) // 5번 호출 될것이며(예상 동작)
.WillOnce(Return(100)) //처음은 100 반환
.WillOnce(Return(150)) //두번째는 150 반환
.WillRepeatedly(Return(200)); // 그 이후부터는 200 계속 반환
class MockFoo {
public:
// 컴파일 안됨!, std::pair<bool, int>
MOCK_METHOD(std::pair<bool, int>, GetPair, ());
// 컴파일 안됨!, std::map<int, double>
MOCK_METHOD(bool, CheckMap, (std::map<int, double>, bool));
};
, 떄문에 구획을 나누는데 어려움이 있음
class MockFoo {
public:
MOCK_METHOD((std::pair<bool, int>), GetPair, ());
MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
};
- ()로 파라미터를 묶어 주면 됨
- argument나 리턴 타입에 ()를 묶는것은 일반적으로 C++에서 불가능 하지만 MOCK_METHOD가 ()를 제거해서 컴파일 됨
class MockFoo {
public:
using BoolAndInt = std::pair<bool, int>;
MOCK_METHOD(BoolAndInt, GetPair, ());
using MapIntDouble = std::map<int, double>;
MOCK_METHOD(bool, CheckMap, (MapIntDouble, bool));
};
- using 사용
class Foo {
public:
...
virtual bool Transform(Gadget* g) = 0;
protected:
virtual void Resume();
private:
virtual int GetTimeOut();
};
class MockFoo : public Foo {
public:
...
MOCK_METHOD(bool, Transform, (Gadget* g), (override));
// Resume, GetTimeOut이 protected, private이어도
// public 섹션에 두어야 한다.
MOCK_METHOD(void, Resume, (), (override));
MOCK_METHOD(int, GetTimeOut, (), (override));
};
template <typename Elem>
class StackInterface {
...
// Must be virtual as we'll inherit from StackInterface.
virtual ~StackInterface();
virtual int GetSize() const = 0;
virtual void Push(const Elem& x) = 0;
};
//템플릿 사용
template <typename Elem>
class MockStack : public StackInterface<Elem> {
...
MOCK_METHOD(int, GetSize, (), (override));
MOCK_METHOD(void, Push, (const Elem& x), (override));//템플릿 타입 사용
};
// CreateConnection, PacketReader에 대한 제품 코드와 테스트 코드 필요
template <class PacketStream>
void CreateConnection(PacketStream* stream) { ... }
template <class PacketStream>
class PacketReader {
public:
void ReadPackets(PacketStream* stream, size_t packet_num);
};
// 제품 코드
CreateConnection<ConcretePacketStream>()
PacketReader<ConcretePacketStream>()
//테스트 코드 1
CreateConnection<MockPacketStream>()
PacketReader<MockPacketStream>()
//테스트 코드 2
MockPacketStream mock_stream;
EXPECT_CALL(mock_stream, ...)...;
.. set more expectations on mock_stream ...
PacketReader<MockPacketStream> reader(&mock_stream);
... exercise reader ...
//제품 클래스
class ConcretePacketStream {
public:
void AppendPacket(Packet* new_packet);
const Packet* GetPacket(size_t packet_number) const;
size_t NumberOfPackets() const;
...
};
class MockPacketStream {
public:
// 제품 클래스로부터 상속 받지 않았다.
// AppendPacket를 mock하지 않았다
MOCK_METHOD(const Packet*, GetPacket, (size_t packet_number), (const));//override 없음
MOCK_METHOD(size_t, NumberOfPackets, (), (const));
...
};
// OpenFile 이라는 일반 함수를 mock 하고 싶을때
// OpenFile에 대한 추상화
class FileInterface {
public:
...
virtual bool Open(const char* path, const char* mode) = 0;
};
// 제품 코드
class File : public FileInterface {
public:
...
bool Open(const char* path, const char* mode) override {
// OpenFile 직접 호출 하는 대신 File의 Open 호출 해야함
return OpenFile(path, mode);
}
};
// mock
class MockFile : public FileInterface {
public:
MOCK_METHOD(bool, Open, (const char* path, const char* mode), (override));
};
- virtual 함수에 대한 성능과 프로파일링에 대해 우려가 있을 경우 non-virtual 메소드로 mock 할 수 있다.
using ::testing::Return;
...
int n = 100;
EXPECT_CALL(tutle_mock, GetX())
.Times(4)
.WillRepeatedly(Return(n++));//100, 101, 102, 103 호출 ?? No
100, 101, 102, 104가 아니라 100 이 4번 호출되는 것으로 설정
EXPECT_CALL은 한번만 해석 된다. !!!
using ::testing::Return;
...
EXPECT_CALL(tutle_mock, GetY())
.WillOnce(Return(100))
.WillOnce(Return(200)) //GetY가 최소 2번 호출되며(100, 200 반환)
.WillRepeatedly(Return(300)); //3번째 부터 300만 반환
Time이 없어도 gmock이 알아서 해석을 하여 Times()를 지정한다.
using ::testing::Return;
...
EXPECT_CALL(tutle_mock, GetY())
.Times(4)
.WillOnce(Return(100));//100이 4번 호출될 것을 기대?? No
4번 호출될 것이며 처음 한번은 100을 반환할 것이고 이후부터는 기본값을 반환한다.
TEST(TestCaseName, TestName) {
MockTurtle turtle_mock;
EXPECT_CALL(turtle_mock, GetY())// GetY 호출에 대한 기대치
.Times(2) // 2번 호출될 것을 기대, 2번 호출되지 않으면 실패
.WillOnce(Return(101)) // GetY 첫번째 호출에서 turtle_mock은 101 반환하도록 정의
.WillOnce(Return(200)); // GetY 두번째 호출에서 turtle_mock은 200 반환하도록 정의
std::cout << turtle_mock.GetY() << std::endl;// 101
std::cout << turtle_mock.GetY() << std::endl;// 200
}
turtle_mock.GetY() 3번 호출하면 실패
.Times(3)으로 바꾸면 3번 호출해도 성공 하지만 경고 발생
using ::testing::Return;
...
EXPECT_CALL(tutle_mock, GetX())
.Times(5) //GetX가 5번 호출될것이다.
.WillOnce(Return(100)) //이 객체는 100을 처음 리턴하고
.WillOnce(Return(150)) //150을 두번째로
.WillRepeatedly(Return(200)); //이 후부터는 계속 200을 리턴할 것이다.
using ::testing::_;
...
EXPECT_CALL(turtle, Forward(_)); // #1
EXPECT_CALL(turtle, Forward(10)) // #2
.Times(2);
Forward(10)
Forward(10)
//Forward(10) // 3번째 호출하면 #2의 기대치가 포화 상태이기에 테스트 실패할 것이다.
Forward(20) // 그러나 3번째에 20이 호출되면 #1의 기대치에 매칭되기에 테스트가 실패하지 않는다.
using ::testing::_;
...
EXPECT_CALL(tutle_mock, Forward(_)); // #1
EXPECT_CALL(tutle_mock, Forward(10)) // #2
.Times(2);
Forward(10) 연속으로 3번 호출할 경우 -> 실패
- 3번 모두 #2 기대값으로 적용되서 검사
- 3 번째 호출 되었을때, 기대(#2)가 포화되었기 때문에 세 번째는 오류가 처리
Forward(10) 연속으로 2번 호출, 세 번째 호출은 Forward(20) -> 성공
- 2번째 까지 #2 처리
- 3번째는 #2로 조건이 맞지 않기 떄문에 #1으로 처리, 성공
기대치 포화 상태가 의미하는것
포화된 기대치 비활성화
// 실패
TEST(TestCaseName, TestName) {
MockTurtle tutle_mock;
EXPECT_CALL(tutle_mock, GetX()) #1
.WillOnce(Return(10));
EXPECT_CALL(tutle_mock, GetX()) #2 : 2번째 호출이 상한에 걸려서 테스트 실패
.WillOnce(Return(10));
std::cout << tutle_mock.GetX() << std::endl;
std::cout << tutle_mock.GetX() << std::endl;
}
// 성공
TEST(TestCaseName, TestName) {
MockTurtle tutle_mock;
EXPECT_CALL(tutle_mock, GetX()) #1
.WillOnce(Return(10));
EXPECT_CALL(tutle_mock, GetX())
.WillOnce(Return(10)).RetiresOnSaturation(); #2 : 상한에 걸리면 비활성화, 2번째 호출은 #1에 매칭
std::cout << tutle_mock.GetX() << std::endl;
std::cout << tutle_mock.GetX() << std::endl;
}
// tutle_mock이 10, 20, 30, ... n * 10으로 호출되는 기대치를 설정하고 싶을때
using ::testing::Return;
...
for (int i = 3; i > 0; i--) {
EXPECT_CALL(tutle_mock, GetX())
.WillOnce(Return(10*i));
}
/*
EXPECT_CALL(mock, get_y())
.WillOnce(::testing::Return(30));
EXPECT_CALL(mock, get_y())
.WillOnce(::testing::Return(20));
EXPECT_CALL(mock, get_y())
.WillOnce(::testing::Return(10))
*/
해석 : 10, 20, 30, ... 순으로 해석될 것이다 ?? No
기대치는 기본적으로 선언 시점에 해석됨
- 마지막(최신) EXPECT_CALL(10)으로 선언되어 있다
- 따라서 두 번째 tutle_mock.GetX()가 호출되면
마지막(최신) EXPECT_CALL() 문이 처리하고 즉시 "상한 위반" 오류가 발생
// 올바른 방법
// 조건 포화되면 기대값을 비활성화 시키면 된다.
using ::testing::Return;
...
for (int i = n; i > 0; i--) {
EXPECT_CALL(tutle_mock, GetX())
.WillOnce(Return(10*i))
.RetiresOnSaturation();
}
for 안에서 EXPECT_CALL을 호출하려 한다면 RetiresOnSaturation를 사용해서
포화된 기대치를 비활성화 시켜야 한다.
// 더 좋은 방법
using ::testing::InSequence;
using ::testing::Return;
...
{
InSequence s;// 역방향으로 열거할 필요 없게 만듬
for (int i = 1; i <= n; i++) {
EXPECT_CALL(tutle_mock, GetX())
.WillOnce(Return(10*i))
.RetiresOnSaturation();
}
}
순서를 지키게끔 강제 함(10,20,30...)
// 개선 전
TEST(TestCaseName, TestName) {
MockTurtle tutle_mock;
{
for (int i = 2; i > 0; i--) { <-- 역순
EXPECT_CALL(tutle_mock, GetX())
.WillOnce(Return(10 * i))
.RetiresOnSaturation();
EXPECT_CALL(tutle_mock, GetY())
.WillOnce(Return(11 * i))
.RetiresOnSaturation();
}
}
std::cout << tutle_mock.GetX() << std::endl; //10 <-- 순서 지키지 않았지만 테스트 통과
std::cout << tutle_mock.GetX() << std::endl; //20
std::cout << tutle_mock.GetY() << std::endl; //11
std::cout << tutle_mock.GetY() << std::endl; //22
}
// 개선 후
TEST(TestCaseName, TestName) {
MockTurtle tutle_mock;
{
InSequence s;
for (int i = 1; i <= 2; i++) { <-- 일부로 역순으로 할 필요 없음
EXPECT_CALL(tutle_mock, GetX())
.WillOnce(Return(10 * i))
.RetiresOnSaturation();
EXPECT_CALL(tutle_mock, GetY())
.WillOnce(Return(11 * i))
.RetiresOnSaturation();
}
}
std::cout << tutle_mock.GetX() << std::endl; //10 <-- GetX, GetY 순서를 지켜야 테스트 통과됨
std::cout << tutle_mock.GetY() << std::endl; //11
std::cout << tutle_mock.GetX() << std::endl; //20
std::cout << tutle_mock.GetY() << std::endl; //21
}
using ::testing::InSequence;
...
TEST(FooTest, DrawsLineSegment) {
...
{
InSequence seq;
EXPECT_CALL(tutle_mock, PenDown());
EXPECT_CALL(tutle_mock, Forward(100));
EXPECT_CALL(tutle_mock, PenUp());
}
Foo();
}
일부만 순서를 지키게 할 수 있다.
using ::testing::Sequence;
...
Sequence s1, s2;
EXPECT_CALL(foo, A())
.InSequence(s1, s2);
EXPECT_CALL(bar, B())
.InSequence(s1);
EXPECT_CALL(bar, C())
.InSequence(s2);
EXPECT_CALL(foo, D())
.InSequence(s2);
specifies the following DAG (where s1 is A -> B, and s2 is A -> C -> D):
+---> B
|
A --|
|
+---> C ---> D
그러나 사용 하는 것을 비추한다.. 테스트를 굉장히 어렵게 만든다.
using ::testing::_;
using ::testing::AnyNumber;
...
EXPECT_CALL(tutle_mock, GoTo(_, _)) // #1, 포괄적인 기대치
.Times(AnyNumber());
EXPECT_CALL(tutle_mock, GoTo(0, 0)) // #2
.Times(2);
GoTo(0,0)가 3번 호출 되었을 경우
- 3번의 호출이 #2와 일치하는지 확인
- 상한 위반 발생
- 이를 회피하려면 #2 기대치가 포화되었을때 비활성화 시키면 됨