【Study Record】Using Google Mocking Framework in Unit Test ( Advanced )

【Study Record】Using Google Mocking Framework in Unit Test ( Advanced )

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:}}

关于创建MOCK类

GMOCK可以MOCK些什么

  • virtual, nonvirtual, concrete, static等类型的方法都可以MOCK
  • virtual方法不一定为纯虚;virtual方法可以在目标类有自己的实现

MOCK protect | private 方法

  • 不论原接口是何种访问类型,MOCK_METHOD*总是放在MOCK类的public访问标识下,以便EXPECT_CALL和ON_CALL可以从外部调用

MOCK重载方法

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&());

MOCK模板中的方法

1
2
3
template
class MockTemp: public Temp{
MOCK_METHOD1_T(v_foo, int ( int x));}

MOCK非虚方法

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自由函数

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. 测试人员需要处理更复杂的抽象关系;但它值得你这么做!

在MOCK函数中使用委托(Delegate)

假设我们建立了类Foo的MOCK类,对于MOCK方法,我们可能需要这些MOCK方法执行一些现有的方法实现(这里称其为Fake方法),或者原有的实现逻辑(这里称其为Real)

Fake委托

我们使用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));

Real委托

方法上和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方法

当对某个MOCK方法不关心时,不要对它定义任何EXPECT_CALL或者ON_CALL,GMOCK会默认给他定义行为

1
DefaultValue::Set() /*该函数可以改变默认行为定义*/

以下方法可以禁止MOCK方法被调用,一旦被调用就产生错误

1
EXPECT_CALL( foo, v_foo(_)).Times(0);

定制MOCK方法的执行顺序

虽然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()后,当该期望行为被满足,就失效,下次不会激发该行为

行为Actions

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......));

模拟边际效果Side Effect

有些函数不是直接传回结果,而是通过出参或改变全局变量来影响结果

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));
/*出参输出一个数组;这个方法对于容器同样适用*/

根据状态改变MOCK方法的行为

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系列函数设定行为

普通的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方法中的参数

当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 将被保存 */

忽略Invoke行为的结果

当设定委托时,有些时候委托方法会返回值,这可能打断测试者原有的意图,可以使用以下方法忽略委托方法的返回值

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方法接受到的参数并不都是委托方法想要的,我们使用如下方法从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…>

忽略MOCK方法中的参数作为

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

你可能感兴趣的:(【Study Record】Using Google Mocking Framework in Unit Test ( Advanced ))