单元测试其二:gmock
the dummy guide
当你和你的小伙伴合作设计一个原型系统。你的模块Painter需要依赖伙伴的模块Turtle,但是因为他比较懒,只是编写了接口,还未实现。这个时候你本该焦头烂额,但是有了gmock,你可以模拟Turtle的行为。
伙伴的turtle模块有诸如向上移动,转弯,获取坐标之类的操作。
第一步要做的是定义一个模拟类,然后继承要mock的类Turtle:
//your module
class Painter {
public:
Painter(cosnt Turtle turtle) {
t = turtle;
bool DrawCircle(int x, int y, int radius) {
private:
Turtle t;
class Turtle {
virtual ~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;
class Painter
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));
};
MOCK_METHOD中的参数依次是 返回值,函数名,参数列表。注意模拟函数 不一定是virtual 的,事实上几乎任何函数都可以模拟。
注意:在老的版本(比如gtest 1.6)并未实现MOCK_METHOD,只能用MOCK_METHODx代替,这里x是参数数目,从0开始。不过,真心建议你的团队使用最新版的 gmock 。
这里总结一下两者的不同点:
MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHODx(MethodName, ReturnType (Args...));
然后你就可以高高兴兴的进行你代码的测试了,例如你想让turtle向下移动,再画一个圆。
#include "path/to/mock-turtle.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::AtLeast; // #1
TEST(PainterTest, CanDrawSomething) {
MockTurtle turtle; // #2
EXPECT_CALL(turtle, PenDown()) // #3
.Times(AtLeast(1));
Painter painter(&turtle); // #4
EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); // #5
}
如果DrawCircle没有调用PenDown()的话,你会非常失望地得到测试失败的信息。
path/to/my_test.cc:119: Failure
Actual function call count doesn't match this expectation:
Actually: never called;
Expected: called at least once.
Stack trace:
...
EXPECT_CALL,第一个参数是mock对象,比如上面的turtle,然后使用gmock串行调用行为函数。
EXPECT_CALL(mock_object, method(matchers))
.Times(cardinality) // 执行次数
.WillOnce(action) // 执行一次
.WillRepeatedly(action); // 重复执行
out-of-box play
我们假设Foo类中有两个方法:
-
有一个方法
Foo::count()
,它还未实现。 -
有一个方法
Foo::picky()
它非常地“挑剔”,只会计算小于50的输入,否则返回-1;
//foo.h
#ifndef _FOO_H_
#define _FOO_H_
class Foo {
public:
virtual int count(int x) = 0; // 未实现
int picky(int x) {
int ret = count(x);
if(ret < 50) {
return ret;
return -1;
#endif
我们想测试picky,但是因为count没实现,这里就是gmock大显身手的时候了:
我们定义一个模拟类
MockFoo
来继承Foo,并且调用一个
MOCK_METHOD
宏
MOCK_METHOD的一般形式为:
MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));
然后我们写一个模拟count的方法:
class MockFoo : public Foo {
public:
MOCK_METHOD(int, count,(int));
};
前三项都是非常清晰的。Specs是修饰符,常用的有
const
,
overide
,
noexcept
等。
之后就是大名鼎鼎的
EXPECT_CALL
了,当然如果你希望测试遇到问题就停止的话,可以选择她的姊妹
ASSERT_CALL
。它的使用方法如下
EXPECT_CALL(mock_object, method(matcher))
.Times(cardinality) // 期望调用cardinality次
.WillOnce(action) // 执行一次
.WillRepeatedly(action); // 执行多次
第一行中mock_object是模拟对象,通常采用
MockFoo mock_foo
定义。method的期望调用的方法。
(matcher)
是可有可无的,
只有存在函数同名重载的情况下
,
(matcher)
才是必须的,用于匹配入参格式。如果为空,表示匹配任意参数,相当于
::testing::_
。
假设我们期望调用count三次,第一次返回3,第二次返回60,之后一直返回40;可以这样写
EXPECT_CALL(mockFoo, count())
.Times(3)
.WillOnce(Return(3))
.WillOnce(Return(60))
.WillRepeatedly(Return(40));
总结 测试函数 http:// main.cc
//main.cc
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "foo.h"
using ::testing::Return;
using ::testing::_;
class MockFoo : public Foo {
public:
MOCK_METHOD1(count, int(int x));
TEST(FooTest, foo) {
MockFoo mockFoo;
EXPECT_CALL(mockFoo, count(_))
.Times(4)
.WillOnce(Return(3))
.WillOnce(Return(60))
.WillRepeatedly(Return(40));
EXPECT_EQ(mockFoo.picky(3), 3);
EXPECT_EQ(mockFoo.picky(60), -1);
EXPECT_EQ(mockFoo.picky(40), 40);
EXPECT_EQ(mockFoo.picky(40), 40);
int main(int argc, char **argv)
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
其中EXPECT_CALL是用于模拟函数行为的关键函数。WillOnce表示运进行一次调用所产生的行为。main函数是 固定且必须的。
- 在命令行运行
g++ -o main main.cc -lgtest -lgmock -lpthread -std=c++11
./main
———————————————強者の境界線——————————————————
高级篇
MOCK类一般形式如下:
class MyMock {
public:
MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));
};
对于复杂的ReturnType,官方建议是打括号或者类型别名:
class MyMock {
public:
// The following 2 lines will not compile due to commas in the arguments:
MOCK_METHOD(std::pair<bool, int>, GetPair, ()); // Error!
MOCK_METHOD(bool, CheckMap, (std::map<int, double>, bool)); // Error!
// One solution - wrap arguments that contain commas in parentheses:
MOCK_METHOD((std::pair<bool, int>), GetPair, ());
MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
// Another solution - use type aliases:
using BoolAndInt = std::pair<bool, int>;
MOCK_METHOD(BoolAndInt, GetPair, ());
using MapIntDouble = std::map<int, double>;
MOCK_METHOD(bool, CheckMap, (MapIntDouble, bool));
};
EXPECT_CALL
EXPECT_CALL一般形式如下:
EXPECT_CALL(mock_object,method_name(matchers...))
顺序
对于不同的链式语句需要注意使用的顺序:
EXPECT_CALL(mock_object, method_name(matchers...))
.With(multi_argument_matcher) // Can be used at most once
.Times(cardinality) // Can be used at most once
.InSequence(sequences...) // Can be used any number of times
.After(expectations...) // Can be used any number of times
.WillOnce(action) // Can be used any number of times
.WillRepeatedly(action) // Can be used at most once
.RetiresOnSaturation(); // Can be used at most once
With-》Times-》InSequence-》After-》WillOnce-》WillRepeatedly-》RetiresOnSaturation
With
.With(
multi_argument_matcher
)
按照
multi_argument_matcher
的限制进行调用。比如:
using ::testing::_;
using ::testing::Lt;
EXPECT_CALL(my_mock, SetPosition(_, _))
.With(Lt());
希望第一个参数小于第二个参数。With最多调用一次,且必须为第一句话。
Times
.Times(
cardinality
)
指定mock函数调用多少次。
基数 | 描述 |
---|---|
AnyNumber() | 调用任意次数 |
AtLeast(n) | 最少调用n次 |
AtMost(n) | 最多调用n次 |
Between(m, n) | 调用[m, n]次 |
Exactly(n) | 恰好调用n次 |
如果没有提供
Times
,那么google test就会进行推测:
-
如果既没有
WillOnce
指定也没有WillRepeatedly
,则推断的基数为Times(1)
。 -
如果有
n 个
WillOnce
子句且没有WillRepeatedly
子句,其中 n >= 1,则推断的基数为Times(n)
。 -
如果有
n 个
WillOnce
子句和一个WillRepeatedly
子句,其中 n >= 0,则推断的基数为Times(AtLeast(n))
。
InSequence
.InSequence(
sequences...
)
指定预期以特定顺序调用模拟函数。
using ::testing::Sequence;
Sequence s1, s2;
EXPECT_CALL(my_mock, Reset())
.InSequence(s1, s2);
EXPECT_CALL(my_mock, GetSize())
.InSequence(s1);
EXPECT_CALL(my_mock, Describe())