【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"
相關文章
相關標籤/搜索