概述
google mock是用来配合google test对C++项目做单元测试的。它依赖于googletest(参见我上篇文章《如何用googletest写单元测试》: http://blog.csdn.net/russell_tao/article/details/7333226),下面我来说说linux上怎么用它来做单元测试。 本文包括:1、如何获取、编译google mock;2、如何使用gmock(下面用gmock来代称google mock)配合gtest做单元测试。3、如何运行单元测试;4、gmock的工作原理。 1、如何获取、编译google mock gmock的当前版本与gtest一样,是1.6.0。可以从这个网址获取:http://code.google.com/p/googlemock/downloads/list。 下载到压缩包解压后,下面我们开始编译出静态库文件(必须得自己编译出),以在我们自己的单元测试工程中使用。 与gtest相同,我们执行完./configure; make后,不能执行make install,理由与上篇相同。 验证这个包有没有问题,依然可以执行如下命令:
如果你看到类似下文的输出屏幕,证明你的机器运行gmock没有问题。
这时还没有编译出我们要的libgmock.a呢。继续在gmock解包目录下执行:
如此,当前目录下会链接出我们需要的libgmock.a。注意,这个gmock.a静态库里,把gtest需要的gtest-all.cc都编译进来了,所以我们的单元测试工程只需要链接libgmock,不再需要链接上文说的libgtest了。
2、如何使用gmock 首先,编译我们自己的单元测试工程时,需要在makefile里加入以下编译选项:-I${GTEST_DIR}/include -I${GMOCK_DIR}/include,这两个目录我们自己从上面的包里拷贝出来即可。链接时,需要加上libgmock.a。 还是以一个例子来说明怎么在mock对象的情况下写单元测试。 我现在有一个生产者消费者网络模型,消费者(例如client)会先发TCP请求到我的SERVER去订阅某个对象。生产者(另一台SERVER)产生关于某个对象的事件后发给我的SERVER后,我的SERVER再把事件发给消费者。 就是这么简单。 我现在想写一个单元测试,主要测试代码逻辑,不想去管网络包的收发这些事情。 我现在有两个类,一个叫CSubscriber,它封装为一个订阅的消费者,功能主要是操作网络,包括网络收发包,协议解析等。另一个叫CSubEventHandler,它主要做逻辑处理,去操作CSubscriber对象,例如epoll返回读事件后,会构造一个CSubscriber对象,然后CSubEventHandler::handleRead方法就来处理这个CSubscriber对象。 我单元测试的目的是,测试CSubEventHandler::handleRead的业务逻辑,我同时也想测试CSubscriber方法里的协议解析逻辑,但是对于CSubscriber封装的读写包部分,我希望可以mock成我想要的网络包。 怎么做呢? a)、先mock一个CSubscriber类如下:
其中,CSubscriber的构造方法必须有一个int型的fd,而readBuf和writeBuf都只接收一个int型的参数,而closeSock方法 没有参数传递。于是我使用了MOCK_METHOD0和MOCK_METHOD1这两个宏来声明想MOCK的方法。这两个宏的使用很简单,解释下:
MOCK_METHOD#1(#2, #3(#4) ) #2是你要mock的方法名称!#1表示你要mock的方法共有几个参数,#4是这个方法具体的参数,#3表示这个方法的返回值类型。 很简单不是?! b)、如果只关心mock方法的返回值。 这里用到一个宏ON_CALL。看例子:
什么意思呢?再用刚才的解释方法:
ON_CALL(#1, #2(#3)).WillByDefault(Return(#4)); #1表示mock对象。就像我上面所说,对CSubscriber我定义了一个Mock类,那么就必须生成相应的mock对象,例如:
#2表示想定义的那个方法名称。上例中我想定义readBuf这个方法的返回值。
#3表示readBuf方法的参数。这里的1000表示,只有调用CSubscriber::readBuf同时传递参数为1000时,才会用到ON_CALL的定义。 #4表示调用CSubscriber::readBuf同时传递参数为1000时,返回blen这个变量的值。 c)、如果还希望mock方法有固定的被调用方式 这里用到宏EXPECT_CALL,看个例子:
很相似吧?最后的Times表示,只希望readBuf在传递参数为1000时,被调用且仅被调用一次。
其实这些宏有很复杂的用法的,例如:
表示,readBuf希望被调用五次,第一次返回100,第二次返回150,后三次返回200。如果不满足,会报错。
d)、实际的调用测试 其实调用跟上篇googletest文章里的测试是一致的,我这里只列下上文的完整用例代码(不包括被测试类的实现代码):
CSubscriber的头文件:
e)、main函数的写法 与gtest相同,唯一的区别是初始化参数,如下:
如此,就可以完整的使用googletest/googlemock做C++工程的单元测试了,确实很简单好用。 Google Test 1. 自定义错误输出:
<span class="pln">ASSERT_EQ</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="pln">x</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln">size</span><span class="pun" style="color: rgb(102, 102, 0);">(),</span><span class="pln"> y</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln">size</span><span class="pun" style="color: rgb(102, 102, 0);">())</span><span class="pln"> </span><span class="pun" style="color: rgb(102, 102, 0);"><<</span><span class="pln"> </span><span class="str" style="color: rgb(0, 136, 0);">"Vectors x and y are of unequal length"</span><span class="pun" style="color: rgb(102, 102, 0);">;</span><span class="pln"> </span><span class="kwd" style="color: rgb(0, 0, 136);">for</span><span class="pln"> </span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="kwd" style="color: rgb(0, 0, 136);">int</span><span class="pln"> i </span><span class="pun" style="color: rgb(102, 102, 0);">=</span><span class="pln"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">;</span><span class="pln"> i </span><span class="pun" style="color: rgb(102, 102, 0);"><</span><span class="pln"> x</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln">size</span><span class="pun" style="color: rgb(102, 102, 0);">();</span><span class="pln"> </span><span class="pun" style="color: rgb(102, 102, 0);">++</span><span class="pln">i</span><span class="pun" style="color: rgb(102, 102, 0);">)</span><span class="pln"> </span><span class="pun" style="color: rgb(102, 102, 0);">{</span><span class="pln"> EXPECT_EQ</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="pln">x</span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln">i</span><span class="pun" style="color: rgb(102, 102, 0);">],</span><span class="pln"> y</span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln">i</span><span class="pun" style="color: rgb(102, 102, 0);">])</span><span class="pln"> </span><span class="pun" style="color: rgb(102, 102, 0);"><<</span><span class="pln"> </span><span class="str" style="color: rgb(0, 136, 0);">"Vectors x and y differ at index "</span><span class="pln"> </span><span class="pun" style="color: rgb(102, 102, 0);"><<</span><span class="pln"> i</span><span class="pun" style="color: rgb(102, 102, 0);">;</span><span class="pln"> </span><span class="pun" style="color: rgb(102, 102, 0);">}</span>
2. ASSERT_* 与EXPECT_*系列的区别:
什么时候用ASSERT,什么时候用EXPECT呢?一般性的原则是,当你希望程序出错后继续运行,以便让代码暴露出更多的潜在错误的时候,使用EXPECT。当你认为某处出错后继续运行没有意义的时候,使用ASSERT。例如,某个对象如果为NULL,后面对它的引用会导致null pointer error,那么最好还是用ASSERT_NE(NULL, obj)。 3. 比较运算符 ASSERT_*, EXPECT_*都有: ASSERT_EQ, ASSERT_NE,ASSERT_LT,ASSERT_GT,ASSERT_LE,ASSERT_GE EXPECT_EQ, EXPECT_NE,EXPECT_LT,EXPECT_GT,EXPECT_LE,EXPECT_GE 另外,GTEST还支持STRING的比较。相关函数有(以ASSERT为例) ASSERT_STREQ,ASSERT_STRNE,ASSERT_STRCASEEQ,ASSERT_STRCASENE 4. TEST与TEST_F之间的区别 TEST_F比TEST强一些的地方在于TEST_F实际上会生成一个新类,该类有SetUp和TearDown函数用于建立和销毁数据结构。 同一个TestCase文件中不能混合使用TEST与TEST_F。
For each test defined with TEST_F(), Google Test will:
Google Mock GoogleMock是个很强大的东西,测试一个模块的时候,可能涉及到和其他模块交互,可以将模块之间的接口mock起来,模拟交互过程。 1. Makefile里面需要加入 -lgmock才能正常连接
2. 可以手工生成Mock类,也可以使用脚本生成 手工:
脚本:
需要mock ob_ms_tablet_location_proxy.h中的ObMergerLocationCacheProxy类,方法如下:
3. 一个类中,只有virtual的member funciton能被mock(试验得到的结论),调用被mock的member function,function行为变成mocked behavior,调用类中没有被mock的member function,function行为与原类相同,不被mock改变。
ps,写了一个简单类,不是virtual居然也能被mock,奇怪。。。。在一个复杂类中,必须是virtual的才能被mock。 这些是实验得到的结论。 从理论上分析,应该必须是virtual才可以。所以,确定哪些函数要被mock,然后在头文件中将其virtual化。不然可别说我没有预先告诉你哦;) 4. 一个被Mock的函数,如果没有在EXPECT_CALL中指定expected behavior,系统将会为其指派默认行为(什么都不做,返回0),并且在屏幕上打印WARNING: GMOCK WARNING: Content | ||||||
---|---|---|---|---|---|---|
|
Mock,更确切地说应该是Mock Object。它究竟是什么?它有什么作用?在这里,我也只能先说说我的理解。 比如当我们在单元测试、模块的接口测试时,当这个模块需要依赖另外一个/几个类,而这时这些个类还没有开发好(那名开发同学比较懒,呵呵),这时我们就可以定义了Mock对象来模拟那些类的行为。
说得更直白一些,就是自己实现一个假的依赖类,对这个类的方法你想要什么行为就可以有什么行为,你想让这个方法返回什么结果就可以返回怎么样的结果。
但这时很多同学往往会提出一个问题:"那既然是我自己实现一个假的依赖类",那和那些市面上的Mock框架有什么关系啊?
这个其实是这样的,这些个Mock框架可以帮助你比较方便、比较轻松地实现这些个假的依赖类。毕竟,如果你实现这么一个假的依赖类的时间花费过场的话,那我还不如等待那位懒惰的同学吧。
Google Mock(简称gmock)是Google在2008年推出的一套针对C++的Mock框架,它灵感取自于jMock、EasyMock、harcreat。它提供了以下这些特性:
- 轻松地创建mock类
- 支持丰富的匹配器(Matcher)和行为(Action)
- 支持有序、无序、部分有序的期望行为的定义
- 多平台的支持
- 新人手册
- Cheat Sheet
- Cheat Sheet中文翻译
- Cookbook
我比较喜欢举例来说明这些个、那些个玩意,因此我们先来看看Google Mock就简单的用法和作用。
- 首先,那个懒惰的同学已经定义好了这么一个接口(万幸,他至少把接口定义好了):
FooInterface.h
- #ifndef FOOINTERFACE_H_
- #define FOOINTERFACE_H_
-
- #include <string>
-
- namespace seamless {
-
- class FooInterface {
- public:
- virtual ~FooInterface() {}
-
- public:
- virtual std::string getArbitraryString() = 0;
- };
-
- } // namespace seamless
-
- #endif // FOOINTERFACE_H_
- FooInterface的析构函数~FooInterface()必须是virtual的
- 在第13行,我们得把getArbitraryString定义为纯虚函数。其实getArbitraryString()也不一定得是纯虚函数,这点我们后面会提到.
现在我们用Google Mock来定义Mock类 FooMock.h
- #ifndef MOCKFOO_H_
- #define MOCKFOO_H_
-
- #include <gmock/gmock.h>
- #include <string>
- #include "FooInterface.h"
-
- namespace seamless {
-
- class MockFoo: public FooInterface {
- public:
- MOCK_METHOD0(getArbitraryString, std::string());
- };
-
- } // namespace seamless
-
- #endif // MOCKFOO_H_
- 第10行我们的MockFoo类继承懒同学的FooInterface
- 第22行我们定义使用gmock中的一个宏(Macro)MOCK_METHOD0来定义MockFoo中的getArbitraryString。Google Mock是需要你根据不同的形参个数来使用不同的Mock Method,我这里getArbitraryString没有函数,就是MOCK_METHOD0了,同理,如果是一个形参,就是MOCK_METHOD1了,以此往下。
FooMain.cc
- #include <cstdlib>
- #include <gmock/gmock.h>
- #include <gtest/gtest.h>
- #include <iostream>
- #include <string>
-
- #include "MockFoo.h"
-
- using namespace seamless;
- using namespace std;
-
- using ::testing::Return;
-
- int main(int argc, char** argv) {
- ::testing::InitGoogleMock(&argc, argv);
-
- string value = "Hello World!";
- MockFoo mockFoo;
- EXPECT_CALL(mockFoo, getArbitraryString()).Times(1).
- WillOnce(Return(value));
- string returnValue = mockFoo.getArbitraryString();
- cout << "Returned Value: " << returnValue << endl;
-
- return EXIT_SUCCESS;
- }
最后我们运行编译,得到的结果如下:
- 第15行,初始化一个Google Mock
- 第18行,声明一个MockFoo的对象:mockFoo
- 第19行,是为MockFoo的getArbitraryString()方法定义一个期望行为,其中Times(1)的意思是运行一次,WillOnce(Return(value))的意思是第一次运行时把value作为getArbitraryString()方法的返回值。
这就是我们最简单的使用Google Mock的例子了,使用起来的确比较简便吧。
典型的流程通过上述的例子,已经可以看出使用Mock类的一般流程如下:
- 引入你要用到的Google Mock名称. 除宏或其它特别提到的之外所有Google Mock名称都位于*testing*命名空间之下.
- 建立模拟对象(Mock Objects).
- 可选的,设置模拟对象的默认动作.
- 在模拟对象上设置你的预期(它们怎样被调用,应该怎样回应?).
从上述的例子中可以看出,当我们针对懒同学的接口定义好了Mock类后,在单元测试/主程序中使用这个Mock类中的方法时最关键的就是对期望行为的定义。
对方法期望行为的定义的语法格式如下:
- EXPECT_CALL(mock_object, method(matcher1, matcher2, ...))
- .With(multi_argument_matcher)
- .Times(cardinality)
- .InSequence(sequences)
- .After(expectations)
- .WillOnce(action)
- .WillRepeatedly(action)
- .RetiresOnSaturation();
- 第1行的mock_object就是你的Mock类的对象
- 第1行的method(matcher1, matcher2, …)中的method就是你Mock类中的某个方法名,比如上述的getArbitraryString;而matcher(匹配器)的意思是定义方法参数的类型,我们待会详细介绍。
- 第3行的Times(cardinality)的意思是之前定义的method运行几次。至于cardinality的定义,我也会在后面详细介绍。
- 第4行的InSequence(sequences)的意思是定义这个方法被执行顺序(优先级),我会再后面举例说明。
- 第6行WillOnce(action)是定义一次调用时所产生的行为,比如定义该方法返回怎么样的值等等。
- 第7行WillRepeatedly(action)的意思是缺省/重复行为。
我稍微先举个例子来说明一下,后面有针对更为详细的说明:
- EXPECT_CALL(mockTurtle, getX()).Times(testing::AtLeast(5)).
- WillOnce(testing::Return(100)).WillOnce(testing::Return(150)).
- WillRepeatedly(testing::Return(200))
- 调用mockTurtle的getX()方法
- 这个方法会至少调用5次
- 第一次被调用时返回100
- 第2次被调用时返回150
- 从第3次被调用开始每次都返回200
Matcher用于定义Mock类中的方法的形参的值(当然,如果你的方法不需要形参时,可以保持match为空。),它有以下几种类型:(更详细的介绍可以参见Google Mock Wiki上的Matcher介绍)
通配符
_ | 可以代表任意类型 |
A() or An() | 可以是type类型的任意值 |
一般比较
Eq(value) 或者 value | argument == value,method中的形参必须是value |
Ge(value) | argument >= value,method中的形参必须大于等于value |
Gt(value) | argument > value |
Le(value) | argument <= value |
Lt(value) | argument < value |
Ne(value) | argument != value |
IsNull() | method的形参必须是NULL指针 |
NotNull() | argument is a non-null pointer |
Ref(variable) | 形参是variable的引用 |
TypedEq(value) | 形参的类型必须是type类型,而且值必须是value |
浮点数的比较
DoubleEq(a_double) | 形参是一个double类型,比如值近似于a_double,两个NaN是不相等的 |
FloatEq(a_float) | 同上,只不过类型是float |
NanSensitiveDoubleEq(a_double) | 形参是一个double类型,比如值近似于a_double,两个NaN是相等的,这个是用户所希望的方式 |
NanSensitiveFloatEq(a_float) | 同上,只不过形参是float |
字符串匹配
这里的字符串即可以是C风格的字符串,也可以是C++风格的。
ContainsRegex(string) | 形参匹配给定的正则表达式 |
EndsWith(suffix) | 形参以suffix截尾 |
HasSubstr(string) | 形参有string这个子串 |
MatchesRegex(string) | 从第一个字符到最后一个字符都完全匹配给定的正则表达式. |
StartsWith(prefix) | 形参以prefix开始 |
StrCaseEq(string) | 参数等于string,并且忽略大小写 |
StrCaseNe(string) | 参数不是string,并且忽略大小写 |
StrEq(string) | 参数等于string |
StrNe(string) | 参数不等于string |
容器的匹配
很多STL的容器的比较都支持==这样的操作,对于这样的容器可以使用上述的Eq(container)来比较。但如果你想写得更为灵活,可以使用下面的这些容器匹配方法:
Contains(e) | 在method的形参中,只要有其中一个元素等于e |
Each(e) | 参数各个元素都等于e |
ElementsAre(e0, e1, …, en) | 形参有n+1的元素,并且挨个匹配 |
ElementsAreArray(array) 或者ElementsAreArray(array, count) | 和ElementsAre()类似,除了预期值/匹配器来源于一个C风格数组 |
ContainerEq(container) | 类型Eq(container),就是输出结果有点不一样,这里输出结果会带上哪些个元素不被包含在另一个容器中 |
Pointwise(m, container) |
上述的一些匹配器都比较简单,我就随便打包举几最简单的例子演示一下吧: 我稍微修改一下之前的Foo.h和MockFoo.h, MockFoo.h 增加了2个方法
- #ifndef MOCKFOO_H_
- #define MOCKFOO_H_
-
- #include <gmock/gmock.h>
- #include <string>
- #include <vector>
- #include "FooInterface.h"
-
- namespace seamless {
-
- class MockFoo: public FooInterface {
- public:
- MOCK_METHOD0(getArbitraryString, std::string());
- MOCK_METHOD1(setValue, void(std::string& value));
- MOCK_METHOD2(setDoubleValues, void(int x, int y));
- };
-
- } // namespace seamless
-
- #endif // MOCKFOO_H_
FooMain.h
- #include <cstdlib>
- #include <gmock/gmock.h>
- #include <iostream>
- #include <string>
-
- #include "MockFoo.h"
-
- using namespace seamless;
- using namespace std;
-
- using ::testing::Assign;
- using ::testing::Eq;
- using ::testing::Ge;
- using ::testing::Return;
-
- int main(int argc, char** argv) {
- ::testing::InitGoogleMock(&argc, argv);
-
- string value = "Hello World!";
- MockFoo mockFoo;
-
- EXPECT_CALL(mockFoo, setValue(testing::_));
- mockFoo.setValue(value);
-
- // 这里我故意犯错
- EXPECT_CALL(mockFoo, setDoubleValues(Eq(1), Ge(1)));
- mockFoo.setDoubleValues(1, 0);
-
- return EXIT_SUCCESS;
- }
- 第22行,让setValue的形参可以传入任意参数
- 另外,我在第26~27行故意犯了个错(为了说明上述这些匹配器的作用),我之前明明让setDoubleValues第二个参数得大于等于1,但我实际传入时却传入一个0。这时程序运行时就报错了:
Unexpected mock function call – returning directly.
Function call: setDoubleValues(1, 0)
Google Mock tried the following 1 expectation, but it didn't match:
FooMain.cc:35: EXPECT_CALL(mockFoo, setDoubleValues(Eq(1), Ge(1)))…
Expected arg #1: is >= 1
Actual: 0
Expected: to be called once
Actual: never called – unsatisfied and active
FooMain.cc:35: Failure
Actual function call count doesn't match EXPECT_CALL(mockFoo, setDoubleValues(Eq(1), Ge(1)))…
Expected: to be called once
Actual: never called – unsatisfied and active
上述的那些匹配器都比较简单,下面我们来看看那些比较复杂的匹配吧。
成员匹配器
Field(&class::field, m) | argument.field (或 argument->field, 当argument是一个指针时)与匹配器m匹配, 这里的argument是一个class类的实例. |
Key(e) | 形参(argument)比较是一个类似map这样的容器,然后argument.first的值等于e |
Pair(m1, m2) | 形参(argument)必须是一个pair,并且argument.first等于m1,argument.second等于m2. |
Property(&class::property, m) | argument.property()(或argument->property(),当argument是一个指针时)与匹配器m匹配, 这里的argument是一个class类的实例. |
还是举例说明一下:
- TEST(TestField, Simple) {
- MockFoo mockFoo;
- Bar bar;
- EXPECT_CALL(mockFoo, get(Field(&Bar::num, Ge(0)))).Times(1);
- mockFoo.get(bar);
- }
-
- int main(int argc, char** argv) {
- ::testing::InitGoogleMock(&argc, argv);
- return RUN_ALL_TESTS();
- }
- 第5行,我们定义了一个Field(&Bar::num, Ge(0)),以说明Bar的成员变量num必须大于等于0。
上面这个是正确的例子,我们为了说明Field的作用,传入一个bar.num = -1试试。
- TEST(TestField, Simple) {
- MockFoo mockFoo;
- Bar bar;
- bar.num = -1;
- EXPECT_CALL(mockFoo, get(Field(&Bar::num, Ge(0)))).Times(1);
- mockFoo.get(bar);
- }
[----------] Global test environment set-up.
[----------] 1 test from TestField
[ RUN ] TestField.Simple
unknown file: Failure
Unexpected mock function call – returning directly.
Function call: get(@0xbff335bc 4-byte object )
Google Mock tried the following 1 expectation, but it didn't match:
FooMain.cc:34: EXPECT_CALL(mockFoo, get(Field(&Bar::num, Ge(0))))…
Expected arg #0: is an object whose given field is >= 0
Actual: 4-byte object , whose given field is -1
Expected: to be called once
Actual: never called – unsatisfied and active
FooMain.cc:34: Failure
Actual function call count doesn't match EXPECT_CALL(mockFoo, get(Field(&Bar::num, Ge(0))))…
Expected: to be called once
Actual: never called – unsatisfied and active
[ FAILED ] TestField.Simple (0 ms)
[----------] 1 test from TestField (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] TestField.Simple
1 FAILED TEST
匹配函数或函数对象的返回值
ResultOf(f, m) | f(argument) 与匹配器m匹配, 这里的f是一个函数或函数对象. |
指针匹配器
Pointee(m) | argument (不论是智能指针还是原始指针) 指向的值与匹配器m匹配. |
复合匹配器
AllOf(m1, m2, …, mn) | argument 匹配所有的匹配器m1到mn |
AnyOf(m1, m2, …, mn) | argument 至少匹配m1到mn中的一个 |
Not(m) | argument 不与匹配器m匹配 |
- EXPECT_CALL(foo, DoThis(AllOf(Gt(5), Ne(10))));
- 传入的参数必须 >5 并且 <= 10
- EXPECT_CALL(foo, DoThat(Not(HasSubstr("blah")), NULL));
- 第一个参数不包含“blah”这个子串
基数用于Times()中来指定模拟函数将被调用多少次|
AnyNumber() | 函数可以被调用任意次. |
AtLeast(n) | 预计至少调用n次. |
AtMost(n) | 预计至多调用n次. |
Between(m, n) | 预计调用次数在m和n(包括n)之间. |
Exactly(n) 或 n | 预计精确调用n次. 特别是, 当n为0时,函数应该永远不被调用. |
Actions(行为)用于指定Mock类的方法所期望模拟的行为:比如返回什么样的值、对引用、指针赋上怎么样个值,等等。 值的返回
Return() | 让Mock方法返回一个void结果 |
Return(value) | 返回值value |
ReturnNull() | 返回一个NULL指针 |
ReturnRef(variable) | 返回variable的引用. |
ReturnPointee(ptr) | 返回一个指向ptr的指针 |
另一面的作用(Side Effects)
Assign(&variable, value) | 将value分配给variable |
使用函数或者函数对象(Functor)作为行为
Invoke(f) | 使用模拟函数的参数调用f, 这里的f可以是全局/静态函数或函数对象. |
Invoke(object_pointer, &class::method) | 使用模拟函数的参数调用object_pointer对象的mothod方法. |
复合动作
DoAll(a1, a2, …, an) | 每次发动时执行a1到an的所有动作. |
IgnoreResult(a) | 执行动作a并忽略它的返回值. a不能返回void. |
这里我举个例子来解释一下DoAll()的作用,我个人认为这个DoAll()还是挺实用的。例如有一个Mock方法:
- virtual int getParamter(std::string* name, std::string* value) = 0
对于这个方法,我这回需要操作的结果是将name指向value的地址,并且得到方法的返回值。
类似这样的需求,我们就可以这样定义期望过程:
- TEST(SimpleTest, F1) {
- std::string* a = new std::string("yes");
- std::string* b = new std::string("hello");
- MockIParameter mockIParameter;
- EXPECT_CALL(mockIParameter, getParamter(testing::_, testing::_)).Times(1).
- WillOnce(testing::DoAll(testing::Assign(&a, b), testing::Return(1)));
- mockIParameter.getParamter(a, b);
- }
默认时,对于定义要的期望行为是无序(Unordered)的,即当我定义好了如下的期望行为:
- MockFoo mockFoo;
- EXPECT_CALL(mockFoo, getSize()).WillOnce(Return(1));
- EXPECT_CALL(mockFoo, getValue()).WillOnce(Return(string("Hello World")));
但有时候我们需要定义有序的(Ordered)的调用方式,即序列 (Sequences) 指定预期的顺序. 在同一序列里的所有预期调用必须按它们指定的顺序发生; 反之则可以是任意顺序.
- using ::testing::Return;
- using ::testing::Sequence;
-
- int main(int argc, char **argv) {
- ::testing::InitGoogleMock(&argc, argv);
-
- Sequence s1, s2;
- MockFoo mockFoo;
- EXPECT_CALL(mockFoo, getSize()).InSequence(s1, s2).WillOnce(Return(1));
- EXPECT_CALL(mockFoo, getValue()).InSequence(s1).WillOnce(Return(
- string("Hello World!")));
- cout << "First:t" << mockFoo.getSize() << endl;
- cout << "Second:t" << mockFoo.getValue() << endl;
-
- return EXIT_SUCCESS;
- }
- 首先在第8行建立两个序列:s1、s2。
- 然后在第11行中,EXPECT_CALL(mockFoo, getSize()).InSequence(s1, s2)说明getSize()的行为优先于s1、s2.
- 而第12行时,EXPECT_CALL(mockFoo, getValue()).InSequence(s1)说明getValue()的行为在序列s1中。
得到的结果如下:
Second: Hello World!
当我尝试一下把mockFoo.getSize()和mockFoo.getValue()的调用对调时试试:
- cout << "Second:t" << mockFoo.getValue() << endl;
- cout << "First:t" << mockFoo.getSize() << endl;
Unexpected mock function call – returning default value.
Function call: getValue()
Returns: ""
Google Mock tried the following 1 expectation, but it didn't match:
FooMain.cc:29: EXPECT_CALL(mockFoo, getValue())…
Expected: all pre-requisites are satisfied
Actual: the following immediate pre-requisites are not satisfied:
FooMain.cc:28: pre-requisite #0
(end of pre-requisites)
Expected: to be called once
Actual: never called – unsatisfied and active
Second:
First: 1
FooMain.cc:29: Failure
Actual function call count doesn't match EXPECT_CALL(mockFoo, getValue())…
Expected: to be called once
Actual: never called – unsatisfied and active
另外,我们还有一个偷懒的方法,就是不要这么傻乎乎地定义这些个Sequence s1, s2的序列,而根据我定义期望行为(EXPECT_CALL)的顺序而自动地识别调用顺序,这种方式可能更为地通用。
- using ::testing::InSequence;
- using ::testing::Return;
-
- int main(int argc, char **argv) {
- ::testing::InitGoogleMock(&argc, argv);
-
- InSequence dummy;
- MockFoo mockFoo;
- EXPECT_CALL(mockFoo, getSize()).WillOnce(Return(1));
- EXPECT_CALL(mockFoo, getValue()).WillOnce(Return(string("Hello World")));
-
- cout << "First:t" << mockFoo.getSize() << endl;
- cout << "Second:t" << mockFoo.getValue() << endl;
-
- return EXIT_SUCCESS;
- }
下面我从我在工作中参与的项目中选取了一个实际的例子来实践Mock。
这个例子的背景是用于搜索引擎的:
- 引擎接收一个查询的Query,比如http://127.0.0.1/search?q=mp3&retailwholesale=0&isuse_alipay=1
- 引擎接收到这个Query后,将解析这个Query,将Query的Segment(如q=mp3、retail_wholesale=0放到一个数据结构中)
- 引擎会调用另外内部模块具体根据这些Segment来处理相应的业务逻辑。
由于Google Mock不能Mock模版方法,因此我稍微更改了一下原本的接口,以便演示:
我改过的例子 我们先来看看引擎定义好的接口们:
VariantField.h 一个联合体,用于保存Query中的Segment的值
- #ifndef VARIANTFIELD_H_
- #define VARIANTFIELD_H_
-
- #include <boost/cstdint.hpp>
-
- namespace seamless {
-
- union VariantField
- {
- const char * strVal;
- int32_t intVal;
- };
-
- } // namespace mlr_isearch_api
-
- #endif // VARIANTFIELD_H_
IParameterInterface.h 提供一个接口,用于得到Query中的各个Segment的值
- #ifndef IPARAMETERINTERFACE_H_
- #define IPARAMETERINTERFACE_H_
-
- #include <boost/cstdint.hpp>
-
- #include "VariantField.h"
-
- namespace seamless {
-
- class IParameterInterface {
- public:
- virtual ~IParameterInterface() {};
-
- public:
- virtual int32_t getParameter(const char* name, VariantField*& value) = 0;
- };
-
- } // namespace
-
- #endif // IPARAMETERINTERFACE_H_
IAPIProviderInterface.h 一个统一的外部接口
- #ifndef IAPIPROVIDERINTERFACE_H_
- #define IAPIPROVIDERINTERFACE_H_
-
- #include <boost/cstdint.hpp>
-
- #include "IParameterInterface.h"
- #include "VariantField.h"
-
- namespace seamless {
-
- class IAPIProviderInterface {
- public:
- IAPIProviderInterface() {}
- virtual ~IAPIProviderInterface() {}
-
- public:
- virtual IParameterInterface* getParameterInterface() = 0;
- };
-
- }
-
- #endif // IAPIPROVIDERINTERFACE_H_
引擎定义好的接口就以上三个,下面是引擎中的一个模块用于根据Query中的Segment接合业务处理的。Rank.h 头文件
- #ifndef RANK_H_
- #define RANK_H_
-
- #include "IAPIProviderInterface.h"
-
- namespace seamless {
-
- class Rank {
- public:
- virtual ~Rank() {}
-
- public:
- void processQuery(IAPIProviderInterface* iAPIProvider);
- };
-
- } // namespace seamless
-
- #endif // RANK_H_
Rank.cc 实现
- #include <cstdlib>
- #include <cstring>
- #include <iostream>
- #include <string>
- #include "IAPIProviderInterface.h"
- #include "IParameterInterface.h"
- #include "VariantField.h"
-
- #include "Rank.h"
-
- using namespace seamless;
- using namespace std;
-
- namespace seamless {
-
- void Rank::processQuery(IAPIProviderInterface* iAPIProvider) {
- IParameterInterface* iParameter = iAPIProvider->getParameterInterface();
- if (!iParameter) {
- cerr << "iParameter is NULL" << endl;
- return;
- }
-
- int32_t isRetailWholesale = 0;
- int32_t isUseAlipay = 0;
-
- VariantField* value = new VariantField;
-
- iParameter->getParameter("retail_wholesale", value);
- isRetailWholesale = (strcmp(value->strVal, "0")) ? 1 : 0;
-
- iParameter->getParameter("is_use_alipay", value);
- isUseAlipay = (strcmp(value->strVal, "0")) ? 1 : 0;
-
- cout << "isRetailWholesale:t" << isRetailWholesale << endl;
- cout << "isUseAlipay:t" << isUseAlipay << endl;
-
- delete value;
- delete iParameter;
- }
-
- } // namespace seamless
- 从上面的例子中可以看出,引擎会传入一个IAPIProviderInterface对象,这个对象调用getParameterInterface()方法来得到Query中的Segment。
- 因此,我们需要Mock的对象也比较清楚了,就是要模拟引擎将Query的Segment传给这个模块。其实就是让=模拟iParameter->getParameter方法:我想让它返回什么样的值就返回什么样的值.
下面我们开始Mock了:
MockIParameterInterface.h 模拟模拟IParameterInterface类
- #ifndef MOCKIPARAMETERINTERFACE_H_
- #define MOCKIPARAMETERINTERFACE_H_
-
- #include <boost/cstdint.hpp>
- #include <gmock/gmock.h>
-
- #include "IParameterInterface.h"
- #include "VariantField.h"
-
- namespace seamless {
-
- class MockIParameterInterface: public IParameterInterface {
- public:
- MOCK_METHOD2(getParameter, int32_t(const char* name, VariantField*& value));
- };
-
- } // namespace seamless
-
- #endif // MOCKIPARAMETERINTERFACE_H_
MockIAPIProviderInterface.h 模拟IAPIProviderInterface类
- #ifndef MOCKIAPIPROVIDERINTERFACE_H_
- #define MOCKIAPIPROVIDERINTERFACE_H_
-
- #include <gmock/gmock.h>
-
- #include "IAPIProviderInterface.h"
- #include "IParameterInterface.h"
-
- namespace seamless {
-
- class MockIAPIProviderInterface: public IAPIProviderInterface{
- public:
- MOCK_METHOD0(getParameterInterface, IParameterInterface*());
- };
-
- } // namespace seamless
-
- #endif // MOCKIAPIPROVIDERINTERFACE_H_
tester.cc 一个测试程序,试试我们的Mock成果
- #include <boost/cstdint.hpp>
- #include <boost/shared_ptr.hpp>
- #include <cstdlib>
- #include <gmock/gmock.h>
-
- #include "MockIAPIProviderInterface.h"
- #include "MockIParameterInterface.h"
- #include "Rank.h"
-
- using namespace seamless;
- using namespace std;
-
- using ::testing::_;
- using ::testing::AtLeast;
- using ::testing::DoAll;
- using ::testing::Return;
- using ::testing::SetArgumentPointee;
-
- int main(int argc, char** argv) {
- ::testing::InitGoogleMock(&argc, argv);
-
- MockIAPIProviderInterface* iAPIProvider = new MockIAPIProviderInterface;
- MockIParameterInterface* iParameter = new MockIParameterInterface;
-
- EXPECT_CALL(*iAPIProvider, getParameterInterface()).Times(AtLeast(1)).
- WillRepeatedly(Return(iParameter));
-
- boost::shared_ptr<VariantField> retailWholesaleValue(new VariantField);
- retailWholesaleValue->strVal = "0";
-
- boost::shared_ptr<VariantField> defaultValue(new VariantField);
- defaultValue->strVal = "9";
-
- EXPECT_CALL(*iParameter, getParameter(_, _)).Times(AtLeast(1)).
- WillOnce(DoAll(SetArgumentPointee<1>(*retailWholesaleValue), Return(1))).
- WillRepeatedly(DoAll(SetArgumentPointee<1>(*defaultValue), Return(1)));
-
- Rank rank;
- rank.processQuery(iAPIProvider);
-
- delete iAPIProvider;
-
- return EXIT_SUCCESS;
- }
- 第26行,定义一个执行顺序,因此在之前的Rank.cc中,是先调用iAPIProvider>getParameterInterface,然后再调用iParameter>getParameter,因此我们在下面会先定义MockIAPIProviderInterface.getParameterInterface的期望行为,然后再是其他的。
- 第27~28行,定义MockIAPIProviderInterface.getParameterInterface的的行为:程序至少被调用一次(Times(AtLeast(1))),每次调用都返回一个iParameter(即MockIParameterInterface*的对象)。
- 第30~34行,我自己假设了一些Query的Segment的值。即我想达到的效果是Query类似http://127.0.0.1/search?retailwholesale=0&isuse_alipay=9。
- 第36~38行,我们定义MockIParameterInterface.getParameter的期望行为:这个方法至少被调用一次;第一次被调用时返回1并将第一个形参指向retailWholesaleValue;后续几次被调用时返回1,并指向defaultValue。
- 第51行,运行Rank类下的processQuery方法。
看看我们的运行成果:
isUseAlipay: 1
从这个结果验证出我们传入的Query信息是对的,成功Mock!
就如我之前所说的,上述的那个例子是我改过的,现实项目中哪有这么理想的结构(特别对于那些从来没有Develop for Debug思想的同学)。
因此我们来看看上述这个例子中实际的代码:其实只有IAPIProviderInterface.h不同,它定义了一个模版函数,用于统一各种类型的接口: IAPIProviderInterface.h 真正的IAPIProviderInterface.h,有一个模版函数
- #ifndef IAPIPROVIDERINTERFACE_H_
- #define IAPIPROVIDERINTERFACE_H_
-
- #include <boost/cstdint.hpp>
- #include <iostream>
-
- #include "IBaseInterface.h"
- #include "IParameterInterface.h"
- #include "VariantField.h"
-
- namespace seamless {
-
- class IAPIProviderInterface: public IBaseInterface {
- public:
- IAPIProviderInterface() {}
- virtual ~IAPIProviderInterface() {}
-
- public:
- virtual int32_t queryInterface(IBaseInterface*& pInterface) = 0;
-
- template<typename InterfaceType>
- InterfaceType* getInterface() {
- IBaseInterface* pInterface = NULL;
- if (queryInterface(pInterface)) {
- std::cerr << "Query Interface failed" << std::endl;
- }
- return static_cast<InterfaceType* >(pInterface);
- }
- };
-
- }
-
- #endif // IAPIPROVIDERINTERFACE_H_
Rank.cc 既然IAPIProviderInterface.h改了,那Rank.cc中对它的调用其实也不是之前那样的。不过其实也就差一行代码:
- // IParameterInterface* iParameter = iAPIProvider->getParameterInterface();
- IParameterInterface* iParameter = iAPIProvider->getInterface<IParameterInterface>();
因为目前版本(1.5版本)的Google Mock还不支持模版函数,因此我们无法Mock IAPIProviderInterface中的getInterface,那我们现在怎么办?
如果你想做得比较完美的话我暂时也没想出办法,我现在能够想出的办法也只能这样:IAPIProviderInterface.h 修改其中的getInterface,让它根据模版类型,如果是IParameterInterface或者MockIParameterInterface则就返回一个MockIParameterInterface的对象
- #ifndef IAPIPROVIDERINTERFACE_H_
- #define IAPIPROVIDERINTERFACE_H_
-
- #include <boost/cstdint.hpp>
- #include <iostream>
-
- #include "IBaseInterface.h"
- #include "IParameterInterface.h"
- #include "VariantField.h"
-
- // In order to Mock
- #include <boost/shared_ptr.hpp>
- #include <gmock/gmock.h>
- #include "MockIParameterInterface.h"
-
- namespace seamless {
-
- class IAPIProviderInterface: public IBaseInterface {
- public:
- IAPIProviderInterface() {}
- virtual ~IAPIProviderInterface() {}
-
- public:
- virtual int32_t queryInterface(IBaseInterface*& pInterface) = 0;
-
- template<typename InterfaceType>
- InterfaceType* getInterface() {
- IBaseInterface* pInterface = NULL;
- if (queryInterface(pInterface) == 0) {
- std::cerr << "Query Interface failed" << std::endl;
- }
-
- // In order to Mock
- if ((typeid(InterfaceType) == typeid(IParameterInterface)) ||
- (typeid(InterfaceType) == typeid(MockIParameterInterface))) {
- using namespace ::testing;
- MockIParameterInterface* iParameter = new MockIParameterInterface;
- boost::shared_ptr<VariantField> retailWholesaleValue(new VariantField);
- retailWholesaleValue->strVal = "0";
-
- boost::shared_ptr<VariantField> defaultValue(new VariantField);
- defaultValue->strVal = "9";
-
- EXPECT_CALL(*iParameter, getParameter(_, _)).Times(AtLeast(1)).
- WillOnce(DoAll(SetArgumentPointee<1>(*retailWholesaleValue), Return(1))).
- WillRepeatedly(DoAll(SetArgumentPointee<1>(*defaultValue), Return(1)));
- return static_cast<InterfaceType* >(iParameter);
- }
- // end of mock
-
- return static_cast<InterfaceType* >(pInterface);
- }
- };
-
- }
-
- #endif // IAPIPROVIDERINTERFACE_H_
- 第33~49行,判断传入的模版函数的类型,然后定义相应的行为,最后返回一个MockIParameterInterface对象
tester.cc
- int main(int argc, char** argv) {
- ::testing::InitGoogleMock(&argc, argv);
-
- MockIAPIProviderInterface* iAPIProvider = new MockIAPIProviderInterface;
-
- InSequence dummy;
- EXPECT_CALL(*iAPIProvider, queryInterface(_)).Times(AtLeast(1)).
- WillRepeatedly(Return(1));
-
- Rank rank;
- rank.processQuery(iAPIProvider);
-
- delete iAPIProvider;
-
- return EXIT_SUCCESS;
- }
- 这里的调用就相对简单了,只要一个MockIAPIProviderInterface就可以了。
这里根据Google Mock Cookbook和我自己试用的一些经验,整理一些试用方面的技巧。
Mock protected、private方法 Google Mock也可以模拟protected和private方法,比较神奇啊(其实从这点上也可以看出,Mock类不是简单地继承原本的接口,然后自己把它提供的方法实现;Mock类其实就等于原本的接口)。
对protected和private方法的Mock和public基本类似,只不过在Mock类中需要将这些方法设置成public。
Foo.h 带private方法的接口
- class Foo {
- private:
- virtual void setValue(int value) {};
-
- public:
- int value;
- };
MockFoo.h
- class MockFoo: public Foo {
- public:
- MOCK_METHOD1(setValue, void(int value));
- };
Google Mock可以Mock模版类,只要在宏MOCK*的后面加上T。
还是类似上述那个例子:
Foo.h 改成模版类
- template <typename T>
- class Foo {
- public:
- virtual void setValue(int value) {};
-
- public:
- int value;
- };
MockFoo.h
- template <typename T>
- class Foo {
- public:
- virtual void setValue(int value) {};
-
- public:
- int value;
- };
当在调用Mock类的方法时,如果之前没有使用EXPECT_CALL来定义该方法的期望行为时,Google Mock在运行时会给你一些警告信息:
Uninteresting mock function call – returning default value.
Function call: setValue(1)
Returns: 0
Stack trace
对于这种情况,可以使用NiceMock来避免:
- // MockFoo mockFoo;
- NiceMock<MockFoo> mockFoo;
当然,另外还有一种办法,就是使用StrictMock来将这些调用都标为失败:
- StrictMock<MockFoo> mockFoo;
这时得到的结果:
Uninteresting mock function call – returning default value.
Function call: setValue(1)
Returns: 0
最后
以上就是长情荔枝为你收集整理的google mock C++单元测试框架的全部内容,希望文章能够帮你解决google mock C++单元测试框架所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复