【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 Centeride

实例:

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类,将不继承原接口类post

1
2
class MockFoo{ /*注意,没有继承Foo*/
MOCK_METHOD( unv_foo, void ());}

将如下caller方法:测试

1
2
void caller(){
unv_foo();}

改写为模板this

1
2
3
template < typename T>
void caller(){
T->unv_foo();}

从而caller的行为将在compile time决定,而非 run timespa

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

在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方法的第1、3、四个参数做为委托方法的入参 */

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
Foo(1);Foo(2);Foo(3)

假设想要确认只有Foo(1)和Foo(3)都invoke了mock.Bar(“a”),而Foo(2)没有invoke任何

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
using ::testing::MockFunction;
TEST(FooTest, InvokesBarCorrectly) {
  MyMock mock;
  MockFunction< void (string check_point_name)> check;
  {
   InSequence s;
   EXPECT_CALL(mock, Bar( "a"
相关文章
相关标签/搜索