文章目录
一、如何使用gMock
1 首先,使用一些简单的宏来描述要模拟的接口,它们将扩展到模拟类的实现;
2 接下来,创建一些模拟对象,并使用直观的语法指定它的期望和行为;
3.然后练习使用模拟对象的代码。一旦出现任何违反预期的情况,gMock将立即捕获。
mock的语法见:docs/reference/mocking.md
1.自定义方法/成员函数的期望行为
对方法期望行为的定义的语法格式如下:
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)的意思是缺省/重复行为。
- eg:
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
2.匹配器
Matcher用于定义Mock类中的方法的形参的值(当然,如果你的方法不需要形参时,可以保持match为空。),它有以下几种类型
通配符
- 这里的_和*A*包括下面的那个匹配符都在Google Mock的*::testing*这个命名空间下,大家要用时需要先引入那个命名空间
一般比较
浮点数的比较
字符串匹配
- 这里的字符串即可以是C风格的字符串,也可以是C++风格的。
容器的匹配
- 很多STL的容器的比较都支持==这样的操作,对于这样的容器可以使用上述的Eq(container)来比较。但如果你想写得更为灵活,可以使用下面的这些容器匹配方法:
- eg:
#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_METHOD(std::string, getArbitraryString, ());
MOCK_METHOD1(void, setValue, (std::string& value));
MOCK_METHOD2(void, setDoubleValues, (int x, int y));
};
} // namespace seamless
#endif // MOCKFOO_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。这时程序运行时就报错了:
unknown file: Failure
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
成员匹配器
- eg:第5行,我们定义了一个Field(&Bar::num, Ge(0)),以说明Bar的成员变量num必须大于等于0。
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();
}
- eg:我们为了说明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);
}
[==========] Running 1 test from 1 test case.
[----------] 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
匹配函数或函数对象的返回值
指针匹配器
复合匹配器
- eg:传入的参数必须 >5 并且 <= 10
EXPECT_CALL(foo, DoThis(AllOf(Gt(5), Ne(10))));
- eg:第一个参数不包含“blah”这个子串
EXPECT_CALL(foo, DoThat(Not(HasSubstr("blah")), NULL));
3.基数Cardinalities
基数用于Times()中来指定模拟函数将被调用多少次
4.行为(Actions)
Actions(行为)用于指定Mock类的方法所期望模拟的行为:比如返回什么样的值、对引用、指针赋上怎么样个值,等等。
- 值的返回
- 副作用(Side Effects)
- 使用函数或者函数对象(Functor)作为行为
- 复合动作
- eg:例如有一个Mock方法:
irtual int getParamter(std::string* name, std::string* value) = 0
对于这个方法,我这回需要操作的结果是将name指向value的地址,并且得到方法的返回值。
类似这样的需求,我们就可以这样定义期望过程:
- 这时就用上了我们的DoAll()了,它将Assign()和Return()结合起来了。
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);
}
5.序列(Sequences)
默认时,对于定义要的期望行为是无序(Unordered)的,即当我定义好了如下的期望行为:
- eg:对于这样的期望行为的定义,我何时调用mockFoo.getValue()或者何时mockFoo.getSize()都可以的。
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- 中。
- 结果:
First: 1
Second: Hello World!
- eg:把mockFoo.getSize()和mockFoo.getValue()的调用对调时试试:
cout << "Second:\t" << mockFoo.getValue() << endl;
cout << "First:\t" << mockFoo.getSize() << endl;
unknown file: Failure
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;
}
6.基于虚函数的Mock实战
VariantField.h
#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_
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_
IParameterInterface.h
#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_
MockIAPIProviderInterface.h
#ifndef MOCKIAPIPROVIDERINTERFACE_H_
#define MOCKIAPIPROVIDERINTERFACE_H_
#include <gmock/gmock.h>
#include "IAPIProviderInterface.h"
#include "IParameterInterface.h"
namespace seamless
{
class MockIAPIProviderInterface : public IAPIProviderInterface
{
public:
MOCK_METHOD(IParameterInterface *, getParameterInterface, ());
};
} // namespace seamless
#endif // MOCKIAPIPROVIDERINTERFACE_H_
MockIParameterInterface.h
#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_METHOD(int32_t, getParameter, (const char *name, VariantField *&value));
};
} // namespace seamless
#endif // MOCKIPARAMETERINTERFACE_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
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_
tester.cc
#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;
// 定义MockIAPIProviderInterface.getParameterInterface的的行为:程序至少被调用一次(Times(AtLeast(1))),每次调用都返回一个iParameter(即MockIParameterInterface*的对象)。
EXPECT_CALL(*iAPIProvider, getParameterInterface()).Times(AtLeast(1)).WillRepeatedly(Return(iParameter));
// 假设了一些Query的Segment的值
boost::shared_ptr<VariantField> retailWholesaleValue(new VariantField);
retailWholesaleValue->strVal = "0";
boost::shared_ptr<VariantField> defaultValue(new VariantField);
defaultValue->strVal = "9";
// 定义MockIParameterInterface.getParameter的期望行为:这个方法至少被调用一次;第一次被调用时返回1并将第一个形参指向retailWholesaleValue;后续几次被调用时返回1,并指向defaultValue。
EXPECT_CALL(*iParameter, getParameter(_, _)).Times(AtLeast(1)).WillOnce(DoAll(SetArgumentPointee<1>(*retailWholesaleValue), Return(1))).WillRepeatedly(DoAll(SetArgumentPointee<1>(*defaultValue), Return(1)));
// 运行Rank类下的processQuery方法。
Rank rank;
rank.processQuery(iAPIProvider);
delete iAPIProvider;
return EXIT_SUCCESS;
}
7.Nice Mocks 和 Strict Mocks
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));
};
当在调用Mock类的方法时,如果之前没有使用EXPECT_CALL来定义该方法的期望行为时,Google Mock在运行时会给你一些警告信息:
- 一般用不上
GMOCK WARNING:
Uninteresting mock function call – returning default value.
Function call: setValue(1)
Returns: 0
Stack trace
对于这种情况,可以使用NiceMock来避免:
- 使用NiceMock来替代之前的MockFoo。
using ::testing::NiceMock;
using ::testing::NaggyMock;
using ::testing::StrictMock;
// MockFoo mockFoo;
NiceMock<MockFoo> mockFoo;
当然,另外还有一种办法,就是使用StrictMock来将这些调用都标为失败:
StrictMock<MockFoo> mockFoo;
这时得到的结果:
unknown file: Failure
Uninteresting mock function call – returning default value.
Function call: setValue(1)
Returns: 0
8.ON_CALL与EXPECT_CALL区别
- ref:gmock_faq.md
如果你确定这些调用是正确的。这将告诉gMock您确实期望调用,并且不应该打印任何警告。
- 写法不同
- The mock function has no default action set, and its return type has no default value set,则使用ON_CALL
- Actual function call count doesn’t match EXPECT_CALL(mock, Bar(_))…
Expected: to be called once
Actual: never called - unsatisfied and active,则使用ON_CALL
using ::testing::_;
...
EXPECT_CALL(foo, Bar(_))
.WillRepeatedly(...);
instead of
using ::testing::_;
...
ON_CALL(foo, Bar(_))
.WillByDefault(...);
二、实战
Turtle 接口定义
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;
};
编写Mock类
#include "gmock/gmock.h" // Brings in gMock.
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
·1从测试名称空间导入gMock名称,以便可以不限定地使用它们(每个文件只需执行一次)。记住,名称空间是个好主意。
2.创建一些模拟对象。
3.指定您对它们的期望(一个方法将被调用多少次?用什么参数?它应该做什么?等等)。
4.练习一些使用模拟的代码;可选地,使用检查结果
5.googletest断言。如果一个模拟方法被调用的次数超过预期,或者使用了错误的参数,您将立即得到一个错误。
6.当一个mock被销毁时,gMock将自动检查对它的所有期望是否已得到满足。
```cpp
#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
}
这个测试检查至少调用了一次PenDown()。如果painter对象没有调用这个方法,你的测试就会失败,并显示如下信息:
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:
...
给Mock设置EXPECT_CALL
宏有两个参数:第一个是模拟对象,然后是方法及其参数参数。注意,两者之间用逗号(,)隔开,而不是句号(.)。如果方法没有重载,宏也可以在没有匹配器的情况下被调用
在gMock中,我们使用EXPECT_CALL()宏来设置模拟方法的期望。一般语法是:
1.Cardinalities: How Many Times Will It Be Called?
在EXPECT_CALL()之后可以指定的第一个子句是Times()。我们将其参数称为基数,因为它告诉调用应该进行多少次
/*
Cardinalities:即.Times()
Times()子句可以省略。如果省略Times(), gMock将推断你的基数。
如果WillOnce()和willrepeated()都不在, EXPECT_CALL(),推断的基数是Times(1)。
如果有n个WillOnce()但没有willrepeated(),其中n >=1,基数为Times(n)。
如果有n个WillOnce()和一个willrepeated(),其中n >=0,基数为Times(AtLeast(n))。
*/
EXPECT_CALL(mock_object, method(matchers))
.Times(cardinality)
.WillOnce(action)
.WillRepeatedly(action);
EXPECT_CALL(mock_object, non-overloaded-method)
.Times(cardinality)
.WillOnce(action)
.WillRepeatedly(action);
/*
它表示turtle对象的GetX()方法将被调用五次第一次返回100,第二次返回150,然后每次返回200。*/
EXPECT_CALL(turtle, GetX())
.Times(5)
.WillOnce(Return(100))
.WillOnce(Return(150))
.WillRepeatedly(Return(200));
2.Matchers: What Arguments Do We Expect?
当mock函数接受实参时,我们可以指定期望的实参
补充:非重载方法的Mock,若下面的接口不是virtual的
// Expects the turtle to move forward by 100 units.
EXPECT_CALL(turtle, Forward(100));
/*
_ as the argument, which means "anything goes":
*/
// Expects that the turtle jumps to somewhere on the x=50 line.
EXPECT_CALL(turtle, GoTo(50, _));
// Expects the turtle moves forward by at least 100.
/*
Ge是Gtest内置的Matchers
Matchers:reference/matchers.md
*/
EXPECT_CALL(turtle, Forward(Ge(100)));
//如果你不关心任何参数,而不是为每个参数指定_,你可以省略形参列表:
// Expects the turtle to move forward.
EXPECT_CALL(turtle, Forward);
// Expects the turtle to jump somewhere.
EXPECT_CALL(turtle, GoTo);
3.Actions: What Should It Do?
首先,如果模拟函数的返回类型是内置类型或指针,该函数有一个默认动作(void函数将返回bool函数返回false,其他函数返回0),在c++ 11及以上版本中,一个模拟函数,其返回类型为default-constructible(即有一个默认构造函数)的默认动作为返回默认构造的值。如果你什么都不说,这种行为将被使用。
其次,如果模拟函数没有默认动作,或者默认动作不适合你,你可以指定每次要采取的行动使用一系列WillOnce()子句后跟一个可选WillRepeatedly ()
ReturnRef(variable)可以返回引用
/*
我们没有显式地编写Times()),并将分别返回100、200和300
*/
using ::testing::Return;
...
EXPECT_CALL(turtle, GetX())
.WillOnce(Return(100))
.WillOnce(Return(200))
.WillOnce(Return(300));
/*
没有explicit Times()),前两次将分别返回100和200,从第三次开始是300。
*/
EXPECT_CALL(turtle, GetY())
.WillOnce(Return(100))
.WillOnce(Return(200))
.WillRepeatedly(Return(300));
注意:EXPECT_CALL()语句只计算action子句一次,即使该操作可能被执行多次。所以你一定要注意副作用。
using ::testing::Return;
...
int n = 100;
EXPECT_CALL(turtle, GetX())
.Times(4)
.WillRepeatedly(Return(n++));
这里不是返回100 101 102,…连续地,这个模拟函数会总是返回100,就像n + +只被评估过一次
using ::testing::Return;
...
EXPECT_CALL(turtle, GetY())
.Times(4)
.WillOnce(Return(100));
记住,每次调用函数时都会使用WillOnce()子句,然后执行默认操作。因此正确答案是turtle.GetY()第一次返回100,但第二次返回0,因为返回0是int函数的默认操作。
4.Using Multiple Expectations
默认情况下,当调用mock方法时,gMock将按照定义期望的相反顺序搜索期望,并在找到与参数匹配的活动期望时停止(您可以将其理解为“更新的规则覆盖旧的。”)。
using ::testing::_;
...
EXPECT_CALL(turtle, Forward(_)); // #1
EXPECT_CALL(turtle, Forward(10)) // #2
.Times(2);
如果Forward(10)连续被调用三次,那么第三次它将是错误,因为最后一个匹配期望(#2)已经饱和。然而,如果第三次Forward(10)调用被Forward(20)取代,那么它就OK了,现在第一个是匹配的期望。
5.Ordered vs Unordered Calls
在这个例子中,我们测试Foo()调用对象中的三个预期函数按写好的顺序。如果一个调用是无序的,它将是一个错误。
using ::testing::InSequence;
...
TEST(FooTest, DrawsLineSegment) {
...
{
InSequence seq;
EXPECT_CALL(turtle, PenDown());
EXPECT_CALL(turtle, Forward(100));
EXPECT_CALL(turtle, PenUp());
}
Foo();
}
6.All Expectations Are Sticky
gMock中的期望在默认情况下是“粘性的”,即使在我们达到它们的调用上限之后,它们仍然是活动的。这是一个需要记住的重要规则,因为它影响到规范的含义.
假设turtle.GoTo(0,0)被调用三次。在第三次中,gMock将执行确保参数与期望#2匹配(请记住,我们总是选择最后匹配期望)。既然我们说过只有两个这样的调用,gMock会立即报告错误。
using ::testing::_;
using ::testing::AnyNumber;
...
EXPECT_CALL(turtle, GoTo(_, _)) // #1
.Times(AnyNumber());
EXPECT_CALL(turtle, GoTo(0, 0)) // #2
.Times(2);
如果你认为它说turtle.GetX()将被调用n次并返回10,20,30,…,连续,三思!问题是,正如我们所说,期望是有粘性的。因此,第二次调用turtle.GetX()时,最后(最新)EXPECT_CALL()语句将匹配,并立即导致“违反上限”错误
using ::testing::Return;
...
for (int i = 1; i <= n; i++) {
EXPECT_CALL(turtle, GetX())
.WillOnce(Return(10*i));
}
正确做法如下:
正确的说法是turtle.GetX()将返回10,20,30,…就是明确地说期望是没有粘性的。换句话说,他们应该在饱和后立即退休:
using ::testing::Return;
...
for (int i = 1; i <= n; i++) {
EXPECT_CALL(turtle, GetX())
.WillOnce(Return(10*i))
.RetiresOnSaturation();
}
三、gMock Cookbook
1.Creating Mock Classes
class MyMock {
public:
MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));
};
前3个参数只是方法声明,分为3部分。第四个参数接受限定符的封闭列表,该限定符将影响生成的方法:
const -使被模拟的方法为const方法。如果需要重写const方法。
override -用override标记方法。如果覆盖,建议使用虚方法。
noexcept -用noexcept标记方法。如果重写noexcept方法。
Calltype(…)-设置方法的调用类型(例如:为STDMETHODCALLTYPE),在Windows中很有用。
ref(…)-用引用限定符标记方法指定。如果重写具有引用的方法则必须资格。例如ref(&)或ref(&&)。
- 参数里面有逗号,需要用()括起来
class MockFoo {
public:
MOCK_METHOD(std::pair<bool, int>, GetPair, ()); // Won't compile!
MOCK_METHOD(bool, CheckMap, (std::map<int, double>, bool)); // Won't compile!
};
//OK
class MockFoo {
public:
MOCK_METHOD((std::pair<bool, int>), GetPair, ());
MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
};
//OK
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));
};
2.Mocking Private or Protected Methods
你必须总是把一个模拟方法定义(MOCK_METHOD)放在模拟类的public:部分中,不管被模拟的方法是公共的,基类中的Protected或private。这允许ON_CALL和EXPECT_CALL从模拟类外部引用模拟函数。
(是的,c++允许子类改变基类中虚函数的访问级别。)
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));
// The following must be in the public section, even though the
// methods are protected or private in the base class.
MOCK_METHOD(void, Resume, (), (override));
MOCK_METHOD(int, GetTimeOut, (), (override));
};
3.Mocking Overloaded Methods
class Foo {
...
// Must be virtual as we'll inherit from Foo.
virtual ~Foo();
// Overloaded on the types and/or numbers of arguments.
virtual int Add(Element x);
virtual int Add(int times, Element x);
// Overloaded on the const-ness of this object.
virtual Bar& GetBar();
virtual const Bar& GetBar() const;
};
class MockFoo : public Foo {
...
MOCK_METHOD(int, Add, (Element x), (override));
MOCK_METHOD(int, Add, (int times, Element x), (override));
MOCK_METHOD(Bar&, GetBar, (), (override));
MOCK_METHOD(const Bar&, GetBar, (), (const, override));
};
如果您没有模拟重载方法的所有版本,编译器将警告基类中的某些方法被隐藏。要解决这个问题,请使用using将它们引入范围:
class MockFoo : public Foo {
...
using Foo::Add;
MOCK_METHOD(int, Add, (Element x), (override));
// We don't want to mock int Add(int times, Element x);
...
};
4.Mocking Class Templates
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));
};
5.Mocking Non-virtual Methods
在这种情况下,模拟类不会与真实类共享一个公共基类,而是与真实类无关,但包含具有相同签名的方法。模拟非虚方法的语法与模拟虚拟方法(只是不添加override):
// A simple packet stream class. None of its members is virtual.
class ConcretePacketStream {
public:
void AppendPacket(Packet* new_packet);
const Packet* GetPacket(size_t packet_number) const;
size_t NumberOfPackets() const;
...
};
// A mock packet stream class. It inherits from no other, but defines
// GetPacket() and NumberOfPackets().
class MockPacketStream {
public:
MOCK_METHOD(const Packet*, GetPacket, (size_t packet_number), (const));
MOCK_METHOD(size_t, NumberOfPackets, (), (const));
...
};
一种方法是对需要使用包流的代码进行模板化。更具体地说,您将为您的代码提供一个数据包流类型的模板类型参数。在生产中,您将使用实例化模板ConcretePacketStream作为类型参数。在测试中,您将使用MockPacketStream实例化相同的模板。例如,你可以这样写:
template <class PacketStream>
void CreateConnection(PacketStream* stream) { ... }
template <class PacketStream>
class PacketReader {
public:
void ReadPackets(PacketStream* stream, size_t packet_num);
};
然后在生产代码中使用CreateConnection()和PacketReader,在生产代码中使用CreateConnection()和PacketReader测试。
MockPacketStream mock_stream;
EXPECT_CALL(mock_stream, ...)...;
.. set more expectations on mock_stream ...
PacketReader<MockPacketStream> reader(&mock_stream);
... exercise reader ...
依赖注入的eg:
源代码
class A{
public:
int Funtion1(B& obj) {
//do something
std::string str = “mock non-virtual methods using templates”;
auto rst = obj.Function2(str);
//do something
}
}
class B{
public:
int Funtion2(std::string _str){ puts(_str.c_str()); }
}
当我们对类A的方法Function1进行UT防护的时候,不关心其中类B的方法Function2的执行结果,这时候该如何对其进行mock呢(Function2是非虚的)?
Gtest提供了依赖注入的方式
将类A修改为:
template <class T1 >
class RefactorA{
public:
int Funtion1(T1 & obj) {
//do something
std::string str = “mock non-virtual methods using templates”;
auto rst = obj.Function2(str);
//do something
}
}
重构之后,类RefactorA变成了类模板,在实例化的时候把依赖的类B显式的“注入”进去,这时候进行UT的时候,就可以把“注入”的类B的方法Function2 进行mock,代码如下:
对类A进行UT测试
class MockB
{
public:
MOCK_METHOD(int , Funtion2, (std::string))
};
class RefactorA _UT : public :: testing::Test
{
protected:
virtual void SetUp(){}
virtual void TearDown(){}
RefactorA < MockB> mockObjA;//实例化模板类
};
TEST_F(RefactorA _UT , Funtion1)
{
//期望类B的方法Function2被调用至少一次,返回值为100,参数为任意字符串
MockB mockObjB;
EXPECT_CALL(mockObjB, Funtion2 (_))
.Times(AtLeast(1))
.WillOnce(Return(100));
auto rst = mockObjA.Function1( mockObjB );//注意这里传入的是mock出来的对象
EXPECT_TRUE( rst );
}
6.Mocking Free Functions
不可能直接模拟一个自由函数(即c风格的函数或静态方法)。如果需要,可以重写代码来使用接口(抽象类)。
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 {
return OpenFile(path, mode);
}
};
不要直接调用一个自由函数(比如OpenFile),而是为它引入一个接口,并有一个具体的子类来调用自由函数:
这可能看起来很麻烦,但在实践中,您经常有多个相关的函数可以放在同一个接口中,因此每个函数语法开销将大大降低。
7.Old-Style MOCK_METHODn Macros
Simple | |
---|---|
Old | MOCK_METHOD1(Foo, bool(int)) |
New | MOCK_METHOD(bool, Foo, (int)) |
Const Method | |
Old | MOCK_CONST_METHOD1(Foo, bool(int)) |
New | MOCK_METHOD(bool, Foo, (int), (const)) |
Method in a Class Template | |
Old | MOCK_METHOD1_T(Foo, bool(int)) |
New | MOCK_METHOD(bool, Foo, (int)) |
Const Method in a Class Template | |
Old | MOCK_CONST_METHOD1_T(Foo, bool(int)) |
New | MOCK_METHOD(bool, Foo, (int), (const)) |
Method with Call Type | |
Old | MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int)) |
New | MOCK_METHOD(bool, Foo, (int), (Calltype(STDMETHODCALLTYPE))) |
Const Method with Call Type | |
Old | MOCK_CONST_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int)) |
New | MOCK_METHOD(bool, Foo, (int), (const, Calltype(STDMETHODCALLTYPE))) |
Method with Call Type in a Class Template | |
Old | MOCK_METHOD1_T_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int)) |
New | MOCK_METHOD(bool, Foo, (int), (Calltype(STDMETHODCALLTYPE))) |
Const Method with Call Type in a Class Template | |
Old | MOCK_CONST_METHOD1_T_WITH_CALLTYPE(STDMETHODCALLTYPE, Foo, bool(int)) |
New | MOCK_METHOD(bool, Foo, (int), (const, Calltype(STDMETHODCALLTYPE))) |
四、SaveArg与SaveArgPointee区别
参考:
struct SomeData { int a; };
class ISomeClass
{
public:
virtual ~ISomeClass() = default;
virtual void foo(SomeData*) = 0;
};
void someFunction(ISomeClass& a)
{
SomeData b{1};
a.foo(&b);
}
class SomeMock : public ISomeClass
{
public:
MOCK_METHOD1(foo, void(SomeData*));
};
SaveArgPointee - 保存指向的数据 (PACKET_DATA *Data)
- SaveArgPointee中Mock函数的接口入参必须是指针类型
- 仅能获取接口中指针返回的值
TEST(TestSomeFoo, shallPassOne)
{
SomeData actualData{};
SomeMock aMock;
EXPECT_CALL(aMock, foo(_)).WillOnce(::testing::SaveArgPointee<0>(&actualData));
someFunction(aMock);
ASSERT_EQ(1, actualData.a);
}
SaveArg - 您将只存储指向不再存在的局部变量的指针(更好用)
- 如果Mock的接口入参是指针类型的,SaveArg也可以干。如果接口入参不是指针类型的只能用SaveArg。
- 仅能获取接口中指针返回的值
TEST(TestSomeFoo, shallPassOne_DanglingPointer)
{
SomeData* actualData;
SomeMock aMock;
EXPECT_CALL(aMock, foo(_)).WillOnce(::testing::SaveArg<0>(&actualData));
someFunction(aMock);
ASSERT_EQ(1, actualData->a);
}
测试:
wangji@script-wang:~/time/$ ./build/unit_test/xx_unittest --gtest_filter=TestSomeFoo.shallPassOne
Note: Google Test filter = TestSomeFoo.shallPassOne
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from TestSomeFoo
[ RUN ] TestSomeFoo.shallPassOne
[ OK ] TestSomeFoo.shallPassOne (0 ms)
[----------] 1 test from TestSomeFoo (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
结论:
-
SaveArgPointee和SaveArg两者的实际使用的应用场景基本是一样的,既可以用指针来获取foo函数参数中指针返回的值,也可以用变量获取;
-
SaveArgPointee和SaveArg是无法改变foo函数中指针参数返回来的值的,仅是获取foo被调用后,指针参数返回的值的;
五、Gmock中Invoke的简单使用
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <vector>
#include <memory>
class BaseMessage {
public:
virtual std::vector<uint8_t> data() const noexcept = 0;
virtual ~BaseMessage() = default;
};
class DerivedMessage : public BaseMessage {
public:
std::vector<uint8_t> data() const noexcept override { return m_data; }
private:
std::vector<uint8_t> m_data;
};
void stub_write(std::vector<uint8_t>)
{
return;
}
// simplified
class Tcp {
public:
virtual void sendMessage(std::shared_ptr<BaseMessage> msg) { stub_write(msg->data());}
};
class SomeClass {
public:
SomeClass(Tcp& tcp) : m_tcp(tcp) {}
void writeDataToRemote(std::shared_ptr<DerivedMessage> derived) {
m_tcp.sendMessage(derived);}
private:
Tcp m_tcp;
};
class MockTcp : public Tcp {
public:
MOCK_METHOD(void, sendMessage, (std::shared_ptr<BaseMessage> msg), (override));
};
TEST(SomeClassTest, writesMessageToSocket){
MockTcp mockTcp;
SomeClass sc(mockTcp);
std::shared_ptr<BaseMessage> bm;
EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(::testing::SaveArg<0>(&bm)); // it will effectively do bm = arg;
const auto derivedMsg = std::make_shared<DerivedMessage>();
sc.writeDataToRemote(derivedMsg);
// verify that the argument that you captured is indeed pointing to the same message
std::cout << derivedMsg.get() << std::endl;
std::cout << bm.get() << std::endl;
}
TEST(SomeClassTest, writesMessageToSocket_Invoke){
MockTcp mockTcp;
SomeClass sc(mockTcp);
std::shared_ptr<BaseMessage> bm;
EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(::testing::Invoke([&bm](auto arg) { bm = arg; }));
const auto derivedMsg2 = std::make_shared<DerivedMessage>();
sc.writeDataToRemote(derivedMsg2);
std::cout << derivedMsg2.get() << std::endl;
std::cout << bm.get() << std::endl;
}
编译并运行
[ FAILED ] TestSomeFoo.shallPassOne (0 ms)
[----------] 1 test from TestSomeFoo (0 ms total)
[----------] 2 tests from SomeClassTest
[ RUN ] SomeClassTest.writesMessageToSocket
0x556aed826d60
0
g_test.cc:83: Failure
Actual function call count doesn't match EXPECT_CALL(mockTcp, sendMessage(_))...
Expected: to be called once
Actual: never called - unsatisfied and active
[ FAILED ] SomeClassTest.writesMessageToSocket (0 ms)
[ RUN ] SomeClassTest.writesMessageToSocket_Invoke
0x556aed826d60
0
g_test.cc:96: Failure
Actual function call count doesn't match EXPECT_CALL(mockTcp, sendMessage(_))...
Expected: to be called once
Actual: never called - unsatisfied and active
[ FAILED ] SomeClassTest.writesMessageToSocket_Invoke (0 ms)
[----------] 2 tests from SomeClassTest (0 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 2 test suites ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 3 tests, listed below:
[ FAILED ] TestSomeFoo.shallPassOne
[ FAILED ] SomeClassTest.writesMessageToSocket
[ FAILED ] SomeClassTest.writesMessageToSocket_Invoke
3 FAILED TESTS
gtest例子:
using ::testing::_; using ::testing::Invoke;
class MockFoo : public Foo {
public:
MOCK_METHOD(int, Sum, (int x, int y), (override));
MOCK_METHOD(bool, ComplexJob, (int x), (override));
};
int CalculateSum(int x, int y) { return x + y; }
int Sum3(int x, int y, int z) { return x + y + z; }
class Helper {
public:
bool ComplexJob(int x);
};
...
MockFoo foo;
Helper helper;
EXPECT_CALL(foo, Sum(_, _))
.WillOnce(&CalculateSum)
.WillRepeatedly(Invoke(NewPermanentCallback(Sum3, 1)));
EXPECT_CALL(foo, ComplexJob(_))
.WillOnce(Invoke(&helper, &Helper::ComplexJob))
.WillOnce([] { return true; })
.WillRepeatedly([](int x) { return x > 0; });
foo.Sum(5, 6); // Invokes CalculateSum(5, 6).
foo.Sum(2, 3); // Invokes Sum3(1, 2, 3).
foo.ComplexJob(10); // Invokes helper.ComplexJob(10).
foo.ComplexJob(-1); // Invokes the inline lambda.
唯一的要求是函数的类型等必须 与mock函数的签名兼容,这意味着后者的参数(如果有的话)可以隐式转换为前者对应的参数,并且前者的返回类型可以隐式转换为后者的类型。
六、Gtest比较自定义类型(一般不会用的到)
GoogleTest.cpp
// GoogleTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "Bar.h"
#include "BarTest.h"
#include "MockFoo.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <iostream>
int main(int argc, char *argv[]) {
testing::GTEST_FLAG(output) = "xml:./Report/";
testing::InitGoogleTest(&argc, argv);
RUN_ALL_TESTS();
return 0;
}
自定义类型: Foo.h文件,定义自定义返回类型,并重载==运算符
#pragma once
class ResultData {
friend bool operator==(const ResultData &lhs, const ResultData &rhs) {
return lhs.m_data == rhs.m_data;
}
public:
void SetDataValue(int val) { m_data = val; }
private:
int m_data = 100;
bool operator==(ResultData &rhs) { return this->m_data == rhs.m_data; }
};
class Foo {
public:
virtual int count() = 0;
virtual ResultData GetResultData() = 0;
};
测试返回的自定义类型的函数
#pragma once
#include "Foo.h"
class Bar {
public:
Bar(Foo *foo) : m_foo(foo) {}
int count() { return m_foo->count(); }
ResultData GetResultData() { return m_foo->GetResultData(); }
private:
Foo *m_foo;
};
Mock:MockFoo.h
#pragma once
#include "Foo.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
class MockFoo : public Foo {
public:
MOCK_METHOD0(count, int());
MOCK_METHOD0(GetResultData, ResultData());
};
test case:文件BarTest.cpp
#include "Bar.h"
#include "MockFoo.h"
#include <gtest/gtest.h>
//定义测试类Bar的固件类BarTest
class BarTest : public ::testing::Test {
protected:
Bar *m_TestImpl;
MockFoo *mockFoo;
void SetUp() override {
mockFoo = new MockFoo();
m_TestImpl = new Bar(mockFoo);
}
void TearDown() override {
if (m_TestImpl) {
delete m_TestImpl;
m_TestImpl = nullptr;
}
}
};
TEST_F(BarTest, count) {
EXPECT_CALL(*mockFoo, count())
.Times(3)
.WillOnce(testing::Return(3))
.WillOnce(testing::Return(6))
.WillOnce(testing::Return(5));
EXPECT_EQ(m_TestImpl->count(), 3);
EXPECT_EQ(m_TestImpl->count(), 6);
EXPECT_EQ(m_TestImpl->count(), 5);
}
TEST_F(BarTest, GetResultData) {
ResultData rd;
rd.SetDataValue(100);
ResultData rd_exp;
rd_exp.SetDataValue(100);
EXPECT_CALL(*mockFoo, GetResultData()).Times(1).WillOnce(testing::Return(rd));
EXPECT_EQ(m_TestImpl->GetResultData(), rd_exp);
}
编译及运行:
▶ [returnCustomType]$ g++ *.cpp -lgmock -lgtest -o main
▶ [returnCustomType]$ ./main
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from BarTest
[ RUN ] BarTest.count
[ OK ] BarTest.count (0 ms)
[ RUN ] BarTest.GetResultData
[ OK ] BarTest.GetResultData (0 ms)
[----------] 2 tests from BarTest (0 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 2 tests.
参考
ref: