A Work log in CISCO China R&D Center
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
class
CFoo{
public
:
int
bar;
virtual
void
v_foo(
int
x) = 0;
virtual
int
v_foo(
char
x) = 0;
/*overloaded v_foo*/
virtual
int
v_foo(
int
x,
int
y) = 0;
/*overloaded v_foo*/
virtual
void
v_foo( vector v);
virtual
int
v_foobar(
char
ch) = 0;
static
void
free_foo(
int
x);
/*free function*/
void
unv_foo(
long
l);
/*concrete function*/
/*un-pure virtual function with inplementation*/
virtual
void
unpure_foo(
int
x) { std::cout<< x << std::endl; }
virtual
Bar& barfoo();
virtual
const
Bar& barfoo();
virtual
bool
b_foo();
|
1
2
3
4
5
6
7
|
class
CFooSon:CFoo{
public
:
virtual
void
v_foo(
int
x){
std::cout<< x <<std::endl;}
virtual
void
v_foo(
double
d){
std::cout<< d <<std::endl:}}
|
1
2
3
4
5
6
7
|
class
MockFoo:
public
Foo{
MOCK_METHOD1(v_foo,
void
(
int
x));
MOCK_METHOD1(v_foo,
int
(
int
x));
MOCK_METHOD2(v_foo,
int
(
int
x,
int
y));}
MOCK_METHOD0(barfoo(), Bar&());
MOCK_CONST_METHOD0(barfoo(),
const
Bar&());
|
1
2
3
|
template
class
MockTemp:
public
Temp{
MOCK_METHOD1_T(v_foo,
int
(
int
x));}
|
MOCK非虚函数的一种方式是,将调用该非虚函数的方法,改写为模板方法,并在调用时动态指定方法是调用真实对象还是MOCK方法
此时的MOCK类,将不继承原接口类
1
2
|
class
MockFoo{
/*注意,没有继承Foo*/
MOCK_METHOD( unv_foo,
void
());}
|
将以下caller方法:
1
2
|
void
caller(){
unv_foo();}
|
改写为模板
1
2
3
|
template
<
typename
T>
void
caller(){
T->unv_foo();}
|
从而caller的行为将在compile time决定,而非 run time
1
2
3
|
caller<Foo>();
/*调用真实的unv_foo非虚方法*/
caller<MockFoo>();
/*调用MOCK的unv_foo非虚方法*/
|
MOCK自由函数(C风格函数或静态方法)的方法,是为该方法写一个抽象类,包含该方法的纯虚函数(一个接口),然后继承它并实现它;在随后的测试中,MOCK这个接口类
1
2
3
4
5
6
7
|
/*foo.h*/
class
i_free_foo{
public
:
virtual
void
call_free_foo() = 0;}
class
c_free_foo:i_free_foo{
public
:
virtual
void
call_free_foo(){ free_foo() }}
|
这样做的缺点有二:1. 需要额外的虚函数调用开销; 2. 测试人员需要处理更复杂的抽象关系;但它值得你这么做!
假设我们建立了类Foo的MOCK类,对于MOCK方法,我们可能需要这些MOCK方法执行一些现有的方法实现(这里称其为Fake方法),或者原有的实现逻辑(这里称其为Real)
我们使用ON_CALL和INVOLK来设置MOCK方法的默认行为委托
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
class
MockFoo:
public
Foo{
public
:
MOCK_METHOD1( v_foo,
void
(
int
x));
/* 注意,当在MOCK类中声明期望行为时 */
/* Fake委托 必须 包含在一个函数中,不能单独出现在public下 */
/* 用于委托的函数实现,不一定要处在原对象的继承串链中, 可以是任何函数的任何实现 */
void
SetFake{
ON_CALL(*
this
/*!*/
, v_foo(_))
.WillByDefault(Invoke(&fake_,&CFooSon::v_foo));
ON_CALL(*
this
/*!*/
, v_foo(5))
.WillByDefault(Invoke(&fake_,&CFooSon::v_foo)
ON_CALL(*
this
/*!*/
, v_foo(Gt(0)))
.WillByDefault(Invoke(&fake_,&CFooSon::v_foo)}
/* 当设置了多个默认行为时,matcher将根据给定的参数执行特定默认行为 */
/* MOCK类中也可以声明和定义方法,这些方法也可以在EXPECT_CALL或ON_CALL中指定委托 */
void
ForDelegation(){
return
"This is Delegation"
; }
private
:
CFooSon fake_;
|
当用于委托的行为方法具有重载时,需要通过静态转换来决定使用哪一个重载方法
1
2
3
|
/* 以下部分替换Invoke(&fake_,&CFooSon::v_foo) */
/* 来指定使用参数为 int版本的v_foo作为委托 */
Invoke(&fake_,
static_cast
(&CFooSon::v_foo));
|
方法上和Fake委托时一样的,只是Invoke的第一个参数不是派生类对象,而是类对象本身
1
2
3
4
5
6
7
8
9
|
class
MockFoo:
public
Foo{
public
:
MOCK_METHOD1( v_foo,
void
(
int
x));
void
SetCall{ ON_CALL(*
this
, v_foo(_))
.WillByDefault(Invoke(&Real_
/*!*/
,&CFooSon::v_foo)); }
private
:
CFoo Real_;
/*!*/
|
我们总是试图MOCK一个纯虚的方法,但不可避免的也会需要MOCK非纯虚方法(比如实例中的unpure_foo),并且有时还需要使用这些非纯虚方法的实现
在这种情况下,MOCK原有的非纯虚方法,会将该非纯虚方法的实现覆盖掉,使得我们无法调用原有的实现
1
2
3
4
5
6
7
|
class
MockFoo:
public
Foo{
public
:
/* 该METHOD声明屏蔽了原有的实现 */
/* 因此下一次直接调用unpure将得不到原始函数 */
MOCK_METHOD1( unpure_foo,
void
(
int
x));
void
FooConcrete(
int
x){
return
Foo::unpure_foo(x);}
|
以这种方式,我们就可以在后续使用中调用原来的实现
1
2
3
4
|
ON_CALL( Foo, unpure_foo(_))
.WillByDefault( Invoke( &foo, &MockFoo::FooConcrete));
EXPECT_CALL( Foo, unpure_foo(_))
.WillOnce( Invoke( &foo, &MockFoo::FooConcrete));
|
1
2
3
4
5
6
|
EXPECT_CALL( Foo, v_foo(1))
.WillOnce(Return(
"foo"
));
/*精确匹配参数*/
EXPECT_CALL( Foo, v_foo( Ge(1), NotNull()))
.WillOnce(Return(
"foo"
));
/*参数一大于1,参数二不为NULL*/
EXPECT_CALL( Foo, v_foo( AllOf( Ge(5), Ne(10))
, Not( HasSubstr(
"bar"
)));
/*参数一大于5且不等于10,参数二不含bar子串*/
|
1
2
3
4
5
|
MockFoo foo; Bar bar1, bar2;
EXPCET_CALL( Const(foo), barfoo())
.WillOnce( Return(bar1));
/*调用const版本foobar*/
EXPECT_CALL( foo, barfoo())
.WillOnce( Return(bar2));
/*调用一般版本foobar*/
|
1
2
3
4
|
EXPECT_CALL( foo, v_foo(_))
.WillRepeatedly( Return(
"foo"
));
/*默认行为*/
EXPECT_CALL( foo, v_foo(Lt(5))))
.WillRepeatedly( Return(
"bar"
));
/*参数小于5时的行为*/
|
1
2
|
EXPECT_CALL( foo, v_foo( Ne(0), _))
.With(AllArgs(Lt()));
/*参数一不等于零,且不大于参数二*/
|
1
2
3
|
EXPECT_CALL( FOO, new_foo(_,_,_))
.With(Allof(Args<0,1>(Lt()), Args<1,2>(Lt())));
/*参数一小于参数二小于参数三*/
|
1
2
3
4
5
|
#include
std::vector v;
const
int
count = count_if(v.begin(), v.end()
, Matches( AllOf( Ge(0),Le(100),Ne(50)));
/*GMOCK的匹配器可以被用于其他地方*/
|
1
2
|
EXPECT_THAT( foo(), StartsWith(
"hello"
));
/*GMOCK的匹配器可以被用于GTEST中*/
|
1
2
3
4
5
|
Foo foo;
Field( &foo::bar, Ge(3));
/*验证foo对象的成员变量bar成员是否大于等于3*/
Property( &foo::v_foo, StartsWith(
"bar"
));
/*验证foo对象的成员函数v_foo的返回值是否以bar开头*/
|
1
2
3
4
|
EXPECT_CALL( foo, v_foo( AllOf(NutNull(), Pointee(Ge(5)))));
/*验证foo.v_foo被CALL的条件是foo的成员指针所指的值不小于5*/
/*Pointee对原始指针和智能指针都有效*/
/*使用Pointee(Pointee(m))嵌套就可以验证指针所指向的指针所指向的地址的值*/
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
MOCK_METHOD1( v_foo,
void
(
const
vector& v));
EXPECT_CALL( MockFoo, v_foo( ElementsAre( 1, Ge(1), _, 5)));
/*函数参数中vector容器中必须有四个数*/
/*这四个数分别为1,不小于1,任何数,以及5,顺序严格*/
/*当顺序并非严格要求时,可以用UnorederedElemtnesAre代替*/
/*这两个容器支持STL中所有标准容器,都支持最多10个数*/
/*超过10个数的,可以用数组代替STL标准容器*/
const
int
expected_vector1[] = {1,2,3,4,5,6,7......}
/*用普通数组代替*/
Matcher expected_vector2 = {1, Ge(5), _, 5......}
/*用元素适配器代替*/
EXPECT_CALL( MockFoo, v_foo(
ElementsArrayAre
/*不是ElementsAre了*/
( expected_vector1, count));
/*当不确定容器内数量时,可以同时传入一个数量参数*/
int
*
const
expected_vector3 =
new
int
[count];
EXPECT_CALL( MockFoo
, v_foo( ElementsArrayAre( expected_vector3, count));
|
虽然EXPECT_CALL比ON_CALL可以设定更多的期望,但这也使得测试用例对实现的依赖性变强,不易维护;一个好的测试用例应当每次只改变一个测试条件
因此,在所有TEST_F的容器中,使用一系列ON_CALL去定义默认的共享的行为,而只在独立的TEST_F中使用EXPECT_CALL定义精确的期望行为
当对某个MOCK方法不关心时,不要对它定义任何EXPECT_CALL或者ON_CALL,GMOCK会默认给他定义行为
1
|
DefaultValue::Set()
/*该函数可以改变默认行为定义*/
|
以下方法可以禁止MOCK方法被调用,一旦被调用就产生错误
1
|
EXPECT_CALL( foo, v_foo(_)).Times(0);
|
虽然MOCK方法会根据EXPECT_CALL或ON_CALL的定义顺序执行,但有时,当前的条件可能更满足后面的期望行为,而非当前应当执行的行为,因而执行顺序被打乱
1
2
3
4
|
/*在需要严格执行顺序的地方定义一个顺序变量即可*/
InSequence s;
EXPECT_CALL( foo, v_foo(_));
EXPECT_CALL( foo, v_foobar(_));
|
有时我们不需要所有期望行为都严格按顺序执行,我们可以定义局部执行顺序
1
2
3
4
5
|
Sequence s1, s2;
/*注意与InSequence进行区分*/
EXPECT_CALL( foo, A()).InSequence( s1, s2);
EXPECT_CALL( foo, B()).InSequence( s1);
EXPECT_CALL( foo, C()).InSequence( s2);
EXPECT_CALL( foo, D()).InSequence( s2);
|
我们不关心B和C谁先执行,但B,C都必须在A后执行;在同一个Sequence变量下的行为,将按照其出现顺序执行,此例中,D会在C后执行
在上例中,如果在某些条件下,B或者C先于A执行,则A就失效(inactive | retired)
1
2
|
EXPECT_CALL(
log
, Log( WARNING_, _, _));
EXPECT_CALL(
log
, Log( WARNING_, _,
"error"
))
|
上例声明,只会有一次带有”error”的WARNING_;当第一个WARNING_中包含”error”,则第二个期望行为会先发生;当第二次WARNING_再到来时,如果里面仍然包含”error”,第二个期望行为还会发生一次,这与先前的定义冲突
1
2
|
EXPECT_CALL(
log
, Log( WARNING_, _,
"error"
))
.RetireOnSaturation();
|
使用RetireOnSaturation()后,当该期望行为被满足,就失效,下次不会激发该行为
Return(x)函数实质上是在一个行为被建立时,保存x的值,在每次EXPECT_CALL被激活时都返回同样的x
有时需要MOCK方法返回一个自定义的类引用
1
2
3
4
5
6
|
class
MockFoo:
public
Foo{
public
: MOCK_METHOD0( barfoo, Bar&());}
MockFoo mfoo; Bar bar;
EXPECT_CALL( foo, barfoo()).WillOnce(ReturnRef(bar));
/*用ReturnRef而非Return返回一个类型引用*/
|
有时需要MOCK方法返回一个指针类型
1
2
3
|
int
x = 0; MockFoo foo;
EXPECT_CALL( foo, GetValue())
.WillRepeatedly(ReturnPointee(&x));
|
期望行为被触发后会依次执行actions,只有最后一个action的返回值会被使用
1
2
|
EXPECT_CALL( foo, Bar(_))
.WillOnce( DoAll( action1, action2, action3......));
|
有些函数不是直接传回结果,而是通过出参或改变全局变量来影响结果
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
class
MockMutator:
public
Mutator{
public
:
MOCK_METHOD2( Mutate,
void
(
bool
mutate,
int
* value));
MOCK_METHOD2( MutateInt,
bool
(
int
* value));
MOCK_METHOD2( MutateArray,
void
(
int
* values,
int
num_values));}
MockMutator mutator;
EXPECT_CALL( mutator, Mutate(
true
, _))
.WillOnce(SetArgPointee(5));
/*将出参设为整数5,这种方法前提是value的类型具有复制构造函数和赋值操作符*/
EXPECT_CALL( mutator, MutateInt(_))
.WillOnce( DoAll( SetArgPointee(5), Return(
true
)));
/*将出参设为5,并返回true*/
int
values[5] = {1,2,3,4,5};
EXPECT_CALL( mutator, MutateArray( NotNull(), 5))
.WillOnce( SetArrayArgument( values, values + 5));
/*出参输出一个数组;这个方法对于容器同样适用*/
|
1
2
3
4
|
InSequence seq;
EXPECT_CALL( MockFoo, b_foo()).WillRepeatedly(Return(
true
));
EXPECT_CALL( MockFoo, v_foo());
EXPECT_CALL( MockFoo, b_foo()).WillRepeatedly(Return(
false
));
|
b_foo在v_foo执行前被调用会返回true,在v_foo被调用后会返回false
1
2
3
4
5
6
7
8
|
class
MockFoo:
public
Foo{
public
: MOCK_METHOD0( barfoo, Bar&());}
MockFoo mfoo; Bar default_bar;
/* here is a default value */
DefaultValue<Bar>::Set(default_bar);
/* DefaultValue */
EXPECT_CALL( foo, barfoo());
foo.barfoo();
/* call will return default_bar */
DefaultValue<Bar>::Clear();
/* Remember to clear the set */
|
普通的Invoke函数行为指定即上述的“Fake委托”
当被Invoke的方法不关心MOCK方法传来何种参数时,使用如下方法Invoke
1
2
3
4
5
6
|
class
MockFoo:
public
Foo{
public
:
MOCK_METHOD1( v_foo,
void
(
int
x));}
void
sub_foo(){
return
"sub"
; }
|
1
2
3
4
|
MockFoo mockfoo;
EXPECT_CALL( mockfoo, v_foo(_))
.WillOnce(
/*!*/
InvokeWithoutArgs(sub_foo));
mockfoo.v_foo();
/* 调用无参的sub_foo */
}
|
当MOCK方法中的参数含有指向函数的指针时,可以这样得到它
1
2
|
class
MockFoo:
public
Foo{
public
: MOCK_METHOD( p_foo,
bool
(
int
n, (*fp)(
int
));}
|
1
2
3
|
MockFoo mockfoo;
EXPECT_CALL(mockfoo, p_foo(_,_)).WillOnce(
/*!*/
InvokeArgument<1>(5));
/*以这种方式就取得了参数*fp并调用了函数(*fp)(int n) */
|
当MOCK方法中含有类型的引用时,可以这样使用它
1
2
|
class
MockFoo:
public
Foo{
public
: MOCK_METHOD( barfoo,
bool
(
bool
(*fp)(
int
n,
const
another&)));}
|
1
2
3
|
MockFoo mockfoo; Another another;
EXPECT_CALL( mockfoo, barfoo(_))
.WillOnce(InvokeArgument<0>(5,
/*!*/
ByRef(another)));
|
通常情况下,局部变量在EXPECT_CALL调用结束后就不再存在,InvokeArgument函数可以保存该值,以供后续测试使用
1
2
3
|
class
MockFoo:
public
Foo{
public
: MOCK_METHOD( barfoo
,
bool
(
bool
(*fp)(
const
double
& x,
const
string& y)));}
|
1
2
3
|
EXPECT_CALL( mockfoo, barfoo(_))
.WillOnce(InvokeArgument<0>(5.0,string(
"hello"
)));
/* 局部变量 5.0 和 hello 将被保存 */
|
当设定委托时,有些时候委托方法会返回值,这可能打断测试者原有的意图,可以使用以下方法忽略委托方法的返回值
1
2
3
4
5
|
int
ReturnInt(
const
Data& data);
string ReturnStr();
class
MockFoo:
public
Foo{
MOCK_METHOD( func1,
void
(
const
Data& data));
MOCK_METHOD( func2,
bool
());}
|
1
2
3
4
5
6
7
8
|
MockFoo mockfoo;
EXPECT_CALL( mockfoo, func1(_))
/*.WillOnce(Invoke(ReturnInt))*/
.WillOnce(
/*!*/
IgnoreResult(Invoke(ReturnInt)));
EXPECT_CALL( mockfoo, func2())
.WillOnce(DoAll( IgnoreResult(ReturnStr)),Return(
true
)));
/*这里执行ReturnStr是所期望的行为*/
/*但不能允许它的返回行为影响后续期望行为的执行*/
|
有时候,MOCK方法接受到的参数并不都是委托方法想要的,我们使用如下方法从MOCK方法的参数中挑选委托参数的入参
1
2
3
4
5
6
7
8
|
bool
v_foo(
int
, vector<
int
>,
int
,
int
, string
,
double
, string,
double
, unsigned,
int
);
/* 待MOCK函数将有10个传入参数 */
bool
ForDelegation(
int
x,
int
y, string z);
/*用于委托的方法只接受三个参数 */
EXPECT_CALL( v_foo,
bool
(_,_,_,_,_,_,_,_,_,_))
.WillOnce(WithArgs<0,2,3>(Invoke( ForDelegation)));
/* 挑选MOCK方法的第一、三、四个参数作为委托方法的入参 */
|
WithArgs<arg1, arg2, arg3…>中的参数可以重复,比如WithArgs<0,1,1,2,2,2…>
1
2
|
int
ModelFuncOne(
const
string& str,
int
x,
int
y) {
return
x*x+y*y }
int
ModelFuncTwo(
const
int
z,
int
x,
int
y) {
return
x*x+y*y }
|
1
2
3
4
|
EXPECT_CALL( mockfoo, v_foo(
"hello"
, 2, 2)
.WillOnce(Invoke(ModelFuncOne));
EXPECT_CALL( mockfoo, v_bar(1,2,2)
.WillOnce(Invoke(ModelFuncTwo));
|
实际上参数一不需要
1
|
int
ModelFunc(
/*!*/
Unused,
int
x,
int
y) {
return
x*x+y*y; }
|
1
2
3
4
|
EXPECT_CALL( mockfoo, v_foo(
"hello"
, 2, 2)
.WillOnce(Invoke(ModelFunc));
EXPECT_CALL( mockfoo, v_bar(1,2,2)
.WillOnce(Invoke(ModelFunc));
|
1
2
|
Action<
bool
(
int
*)> set_flag = DoAll(SetArgPointee<0>(5), Return(
true
));
/* use set_flag in .WillOnce or .WillRepeatedly */
|
注意:当行为具有内部状态时,多次连续调用可能导致结果出乎意料
不要让编译器为你合成构造/析构函数
只在mock_*.h中定义构造/析构函数
在mock_*.cpp中实现构造/析构函数
在一个测试TEST*的最后调用如下方法
1
|
Mock::VerifyAndClearExpectations(MockObj);
|
将会强制GMock检查mock类对象是否还有期望行为未完成
该方法返回布尔值,可以作为EXPECT_*或ASSERT_*的参数
1
|
Mock::VerifyAndClear(MockObj);
|
该方法除具备上一个方法的全部功能外,还可以清除所有的ON_CALL定义
假设有一连串的调用
1
|
|