google mock C++单元测试框架

匿名 (未验证) 提交于 2019-12-03 00:22:01
Google Mock 入门概述什么是Mock?

Mock,更确切地说应该是Mock Object。它究竟是什么?它有什么作用?在这里,我也只能先说说我的理解。 比如当我们在单元测试、模块的接口测试时,当这个模块需要依赖另外一个/几个类,而这时这些个类还没有开发好(那名开发同学比较懒,呵呵),这时我们就可以定义了Mock对象来模拟那些类的行为。
说得更直白一些,就是自己实现一个假的依赖类,对这个类的方法你想要什么行为就可以有什么行为,你想让这个方法返回什么结果就可以返回怎么样的结果。
但这时很多同学往往会提出一个问题:"那既然是我自己实现一个假的依赖类",那和那些市面上的Mock框架有什么关系啊?
这个其实是这样的,这些个Mock框架可以帮助你比较方便、比较轻松地实现这些个假的依赖类。毕竟,如果你实现这么一个假的依赖类的时间花费过场的话,那我还不如等待那位懒惰的同学吧。

Google Mock概述

Google Mock(简称gmock)是Google在2008年推出的一套针对C++的Mock框架,它灵感取自于jMockEasyMockharcreat。它提供了以下这些特性:

  • 轻松地创建mock类
  • 支持丰富的匹配器(Matcher)和行为(Action)
  • 支持有序、无序、部分有序的期望行为的定义
  • 多平台的支持
参考文档Google Mock使用最简单的例子

我比较喜欢举例来说明这些个、那些个玩意,因此我们先来看看Google Mock就简单的用法和作用。

  • 首先,那个懒惰的同学已经定义好了这么一个接口(万幸,他至少把接口定义好了):

FooInterface.h

  1. #ifndef
  2. #define

  3. #include<string>

  4. namespace{

  5. classFooInterface{
  6. public:
  7. virtual~FooInterface(){}

  8. public:
  9. virtual::string()=0;
  10. };

  11. }// namespace seamless

  12. #endif// FOOINTERFACE_H_
这里需要注意几点:

  • FooInterface的析构函数~FooInterface()必须是virtual的
  • 在第13行,我们得把getArbitraryString定义为纯虚函数。其实getArbitraryString()也不一定得是纯虚函数,这点我们后面会提到.

FooMock.h

  1. #ifndef
  2. #define

  3. #include<gmock/gmock.h>
  4. #include<string>
  5. #include"FooInterface.h"

  6. namespace{

  7. classMockFoo:publicFooInterface{
  8. public:
  9. (getArbitraryString,::string());
  10. };

  11. }// namespace seamless

  12. #endif// MOCKFOO_H_
我们稍微来解释一下这个Mock类的定义:

  • 第10行我们的MockFoo类继承懒同学的FooInterface
  • 第22行我们定义使用gmock中的一个宏(Macro)MOCK_METHOD0来定义MockFoo中的getArbitraryString。Google Mock是需要你根据不同的形参个数来使用不同的Mock Method,我这里getArbitraryString没有函数,就是MOCK_METHOD0了,同理,如果是一个形参,就是MOCK_METHOD1了,以此往下。

FooMain.cc

  1. #include<cstdlib>
  2. #include<gmock/gmock.h>
  3. #include<gtest/gtest.h>
  4. #include<iostream>
  5. #include<string>

  6. #include"MockFoo.h"

  7. usingnamespace;
  8. usingnamespace;

  9. using::testing::Return;

  10. int(int,char**){
  11. ::testing::InitGoogleMock(&argc,);

  12. string="Hello World!";
  13. MockFoo;
  14. (mockFoo,()).Times(1).
  15. WillOnce(Return(value));
  16. string=.getArbitraryString();
  17. <<"Returned Value: "<<<<;

  18. return;
  19. }

最后我们运行编译,得到的结果如下:

Returned Value: Hello World!

在这里:

  • 第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类中的方法时最关键的就是对期望行为的定义。
对方法期望行为的定义的语法格式如下:

  1. EXPECT_CALL(mock_object,(matcher1,,...))
  2. .With(multi_argument_matcher)
  3. .Times(cardinality)
  4. .InSequence(sequences)
  5. .After(expectations)
  6. .WillOnce(action)
  7. .WillRepeatedly(action)
  8. .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)的意思是缺省/重复行为。

我稍微先举个例子来说明一下,后面有针对更为详细的说明:

  1. EXPECT_CALL(mockTurtle,()).Times(testing::AtLeast(5)).
  2. WillOnce(testing::Return(100)).WillOnce(testing::Return(150)).
  3. WillRepeatedly(testing::Return(200))
这个期望行为的定义的意思是:

  • 调用mockTurtle的getX()方法
  • 这个方法会至少调用5次
  • 第一次被调用时返回100
  • 第2次被调用时返回150
  • 从第3次被调用开始每次都返回200
Matcher(匹配器)

Matcher用于定义Mock类中的方法的形参的值(当然,如果你的方法不需要形参时,可以保持match为空。),它有以下几种类型:(更详细的介绍可以参见Google Mock Wiki上的Matcher介绍
通配符

_可以代表任意类型
A() or An()可以是type类型的任意值
这里的_和*A*包括下面的那个匹配符都在Google Mock的*::testing*这个命名空间下,大家要用时需要先引入那个命名空间

一般比较

Eq(value) 或者 valueargument == 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.hMockFoo.hMockFoo.h

  1. #ifndef
  2. #define

  3. #include<gmock/gmock.h>
  4. #include<string>
  5. #include<vector>
  6. #include"FooInterface.h"

  7. namespace{

  8. classMockFoo:publicFooInterface{
  9. public:
  10. (getArbitraryString,::string());
  11. (setValue,void(std::string&));
  12. (setDoubleValues,void(int,int));
  13. };

  14. }// namespace seamless

  15. #endif// MOCKFOO_H_

FooMain.h

  1. #include<cstdlib>
  2. #include<gmock/gmock.h>
  3. #include<iostream>
  4. #include<string>

  5. #include"MockFoo.h"

  6. usingnamespace;
  7. usingnamespace;

  8. using::testing::Assign;
  9. using::testing::Eq;
  10. using::testing::Ge;
  11. using::testing::Return;

  12. int(int,char**){
  13. ::testing::InitGoogleMock(&argc,);

  14. string="Hello World!";
  15. MockFoo;

  16. (mockFoo,(testing::_));
  17. .setValue(value);

  18. // 这里我故意犯错
  19. (mockFoo,(Eq(1),Ge(1)));
  20. .setDoubleValues(1,0);

  21. return;
  22. }
  • 第22行,让setValue的形参可以传入任意参数
  • 另外,我在第26~27行故意犯了个错(为了说明上述这些匹配器的作用),我之前明明让setDoubleValues第二个参数得大于等于1,但我实际传入时却传入一个0。这时程序运行时就报错了:
unknown file: Failure


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

FooMain.cc:35: Failure
Actual function call count doesn't match EXPECT_CALL(mockFoo, setDoubleValues(Eq(1), Ge(1)))…
Expected: to be called once

上述的那些匹配器都比较简单,下面我们来看看那些比较复杂的匹配吧。
成员匹配器

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类的实例.

还是举例说明一下:

  1. TEST(TestField,Simple){
  2. MockFoo;
  3. Bar;
  4. (mockFoo,get(Field(&Bar::num,Ge(0)))).Times(1);
  5. .get(bar);
  6. }

  7. int(int,char**){
  8. ::testing::InitGoogleMock(&argc,);
  9. return();
  10. }
这里我们使用Google Test来写个测试用例,这样看得比较清楚。

  • 第5行,我们定义了一个Field(&Bar::num, Ge(0)),以说明Bar的成员变量num必须大于等于0。

上面这个是正确的例子,我们为了说明Field的作用,传入一个bar.num = -1试试。

  1. TEST(TestField,Simple){
  2. MockFoo;
  3. Bar;
  4. .=-1;
  5. (mockFoo,get(Field(&Bar::num,Ge(0)))).Times(1);
  6. .get(bar);
  7. }
运行是出错了:

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from TestField
[ RUN ] TestField.Simple
unknown file: Failure



Google Mock tried the following 1 expectation, but it didn't match:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!