注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

Google Mock进阶篇 [7] (Google Mock Cookbook译文)  

2012-05-03 23:20:34|  分类: C++ |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Invoking a Function / Method / Functor Without Arguments

    Invoke()在做一些比较复杂的动作时非常有用。它将Mock函数的参数传递给被调用的函数或是仿函数,即被调函数有完整的上下文。如果被调函数或仿函数对其中一些或全部参数不感兴趣,它可以简单地忽略它们。

    但一个单元测试者通常想调用一个不带任何一个Mock函数参数的函数。Invoke允许你使用一个包装函数丢弃所有的参数。不消说,这种工作是无趣和并将测试意图晦涩化。

    InvokeWithoutArgs()是用来解决这个问题的。它类似Invoke(),只是它不需要将递Mock函数的参数给被调者。下面是一个例子:

using ::testing::_;

using ::testing::InvokeWithoutArgs;

 

class MockFoo : public Foo {

 public:

  MOCK_METHOD1(ComplexJob, bool(int n));

};

 

bool Job1() { ... }

...

 

MockFoo foo;

EXPECT_CALL(foo, ComplexJob(_))

  .WillOnce(InvokeWithoutArgs(Job1));

 

foo.ComplexJob(10);  // Invokes Job1().

Invoking an Argument of the Mock Function

    有时一个Mock函数会接收一个函数指针或是一个仿函数( 换言之,一个”callable” )参数,比如:

class MockFoo : public Foo {

 public:

  MOCK_METHOD2(DoThis, bool(int n, bool (*fp)(int)));

};

    你也许想调用这个函数指针参数:

using ::testing::_;

...

MockFoo foo;

EXPECT_CALL(foo, DoThis(_, _))

  .WillOnce(...);

// Will execute (*fp)(5), where fp is the

// second argument DoThis() receives.

    啊,你需要引用一个Mock函数的参数,但C++还没有lambda表示式,所以你需要定义你自己的动作。:-( 或是你真需要这么做吗?

    嗯,Google Mock有一个动作特地来解决这个问题:

InvokeArgument<N>(arg_1, arg_2, ..., arg_m)

    它会调用Mock函数接收到的第N个参数,并将arg_1, arg2, ..., arg_m作为参数。无论参数是一个函数指针或是一个仿函数,Google Mock都可以处理。

    使用它,你可以写:

using ::testing::_;

using ::testing::InvokeArgument;

...

  EXPECT_CALL(foo, DoThis(_, _))

     .WillOnce(InvokeArgument<1>(5));

  // Will execute (*fp)(5), where fp is the

  // second argument DoThis() receives.

    如果一个函数是有一个参数是引用呢?没问题,把它放到ByRef()中:

...

  MOCK_METHOD1(Bar, bool(bool (*fp)(int, const Helper&)));

...

using ::testing::_;

using ::testing::ByRef;

using ::testing::InvokeArgument;

...

 

MockFoo foo;

Helper helper;

...

EXPECT_CALL(foo, Bar(_))

  .WillOnce(InvokeArgument<0>(5, ByRef(helper)));

// ByRef(helper) guarantees that a reference to helper, not a copy of it,

// will be passed to the callable.

    如果函数指针接收的是引用参数,但我们没有将参数放到ByRef()中呢?那么InvokeArgument()会拷贝这个参数,将传递拷贝后的值的引用给函数指针,而不是原来值的引用。这在参数是一个临时变量时特别方便:

...

  MOCK_METHOD1(DoThat, bool(bool (*f)(const double& x, const string& s)));

...

MOCK_METHOD1(DoThat, bool(bool (*f)(const double& x, const string& s)));

...

using ::testing::_;

using ::testing::InvokeArgument;

...

 

MockFoo foo;

...

EXPECT_CALL(foo, DoThat(_))

   .WillOnce(InvokeArgument<0>(5.0, string("Hi")));

// Will execute (*f)(5.0, string("Hi")), where f is the function pointer

// DoThat() receives.  Note that the values 5.0 and string("Hi") are

// temporary and dead once the EXPECT_CALL() statement finishes.  Yet

// it's fine to perform this action later, since a copy of the values

// are kept inside the InvokeArgument action.

Ignoring an Action’s Result

    有时你有一个返回值的动作,但你需要一个返回void的动作( 也许你想在一个返回voidMock函数中用它,或是它在DoAll()中要用它,但它不是DoAll()中最后一个 )IgnoreResult()允许你实现这个功能。比如:

using ::testing::_;

using ::testing::Invoke;

using ::testing::Return;

 

int Process(const MyData& data);

string DoSomething();

 

class MockFoo : public Foo {

 public:

  MOCK_METHOD1(Abc, void(const MyData& data));

  MOCK_METHOD0(Xyz, bool());

};

...

 

MockFoo foo;

EXPECT_CALL(foo, Abc(_))

// .WillOnce(Invoke(Process));

// The above line won't compile as Process() returns int but Abc() needs

// to return void.

  .WillOnce(IgnoreResult(Invoke(Process)));

 

EXPECT_CALL(foo, Xyz())

  .WillOnce(DoAll(IgnoreResult(Invoke(DoSomething)),

  // Ignores the string DoSomething() returns.

              Return(true)));

    注意你不能IgnoreResult()用在一个已经是返回void的动作上。如果你这样做,你会得到一个丑陋的编译错误。

Selecting an Action’s Arguments

    假使你有一个Mock函数Foo(),它接受七个参数,并且你想在Foo调用时使用一个自定义的动作。但问题是,这个自定义的动作只有三个参数:

using ::testing::_;

using ::testing::Invoke;

...

MOCK_METHOD7(Foo, bool(bool visible, const string& name, int x, int y,

                   const map<pair<int, int>, double>& weight,

                   double min_weight, double max_wight));

...

 

bool IsVisibleInQuadrant1(bool visible, int x, int y) {

return visible && x >= 0 && y >= 0;

}

...

 

EXPECT_CALL(mock, Foo(_, _, _, _, _, _, _))

   .WillOnce(Invoke(IsVisibleInQuadrant1));  // Uh, won't compile. :-(

    为了取悦编译器,你可以定义一个配接器,它有着与Foo()相同的定义,然后用它调用自定义动作:

using ::testing::_;

using ::testing::Invoke;

 

bool MyIsVisibleInQuadrant1(bool visible, const string& name, int x, int y,

                     const map<pair<int, int>, double>& weight,

                     double min_weight, double max_wight) {

return IsVisibleInQuadrant1(visible, x, y);

}

...

 

EXPECT_CALL(mock, Foo(_, _, _, _, _, _, _))

   .WillOnce(Invoke(MyIsVisibleInQuadrant1));  // Now it works.

    但这要写不笨拙吗?

    Google Mock提供了一个通用的动作配接器,所以你可以把时间用到更重要的事情上去,而不是写你自己的配接器。下面是它的语法:

WithArgs<N1, N2, ..., Nk>(action)

    它创建一个动作,将Mock函数的参数传给内部的动作,用WithArgs,我们前面的例子可以写为:

using ::testing::_;

using ::testing::Invoke;

using ::testing::WithArgs;

...

EXPECT_CALL(mock, Foo(_, _, _, _, _, _, _))

  .WillOnce(WithArgs<0, 2, 3>(Invoke(IsVisibleInQuadrant1)));

  // No need to define your own adaptor.

    为了更好的可读性,Google Mock提供给你了:

l  WithoutArgs(action)当内部动作不接受参数

l  WithArg<N>(action)( Arg后没有s )当内部动作接受一个参数。

正如你所认识到的,InvokeWithoutArgs(...)只是WithoutArgs(Invoke(...))的语法糖。

这里有几个小提示:

l  WithArgs内部的动作并不一定要是Invoke(),它可以是任意的。

l  在参数列表中的参数可以重复的,比如WithArgs<2,3,3,5>(...)

l  你可以改变参数的顺序,比如WithArgs<3, 2, 1>(...)

l  所选的参数类型并不一定要完全匹配内部动作的定义。只要它们可以隐式地被转换成内部动作的相应参数就可以了。例如,如果Mock函数的第4个参数是int,而my_action接受一个double参数,WithArg<4>(my_action)可以工作。

Ignoring Arguments in Action Functions

Selecting-an-action’s-arguments中介绍了一种使参数不匹配的动作和Mock函数结合使用的方法。但这种方法的缺点是要将动作封装到WithArgs<...>()中,这会使测试者感到麻烦。

如果你定义要用于Invoke*的一个函数,方法,或是仿函数,并且你对它的一些函数不感兴趣,另一种做法是声明你不感兴趣的参数为Unused。这会吏定义更清爽,并在不感兴趣的参数发生变化时更健壮。而且它可以增加一个动作函数被重用的可能性。比如,有:

MOCK_METHOD3(Foo, double(const string& label, double x, double y));

MOCK_METHOD3(Bar, double(int index, double x, double y));

你除了可以像下面一样写:

using ::testing::_;

using ::testing::Invoke;

 

double DistanceToOriginWithLabel(const string& label, double x, double y) {

  return sqrt(x*x + y*y);

}

 

double DistanceToOriginWithIndex(int index, double x, double y) {

  return sqrt(x*x + y*y);

}

...

 

EXEPCT_CALL(mock, Foo("abc", _, _))

  .WillOnce(Invoke(DistanceToOriginWithLabel));

EXEPCT_CALL(mock, Bar(5, _, _))

  .WillOnce(Invoke(DistanceToOriginWithIndex));

    你还可以写:

using ::testing::_;

using ::testing::Invoke;

using ::testing::Unused;

 

double DistanceToOrigin(Unused, double x, double y) {

  return sqrt(x*x + y*y);

}

...

 

EXEPCT_CALL(mock, Foo("abc", _, _))

  .WillOnce(Invoke(DistanceToOrigin));

EXEPCT_CALL(mock, Bar(5, _, _))

  .WillOnce(Invoke(DistanceToOrigin));

Sharing Actions

如匹配器一样,Google Mock动作对象中也有一个指针指向引用计数的实现对象。所以拷贝动作是允许的并且也是高效的。当最后一个引用实现对象的动作死亡后,现实对象会被delete

如果你有一些想重复使用的复杂动作。你也许不想每次都重新产生一次。如果这个动作没有一个内部状态( 比如:它在每次调用都做相同的事),你可以将它赋值给一个动作变量,以后就可以重复使用这个变量了,比如:

Action<bool(int*)> set_flag = DoAll(SetArgPointee<0>(5),

                           Return(true));

... use set_flag in .WillOnce() and .WillRepeatedly() ...

    但是,如果一个动作有自己的状态,那你共享这个动作对象时,你也许会得到一些意外的结果。假设你有一个动作工厂IncrementCounter(init),它创建一个动作,这个动作中的计数器初始值是init,每次调用增加计数器的值并返回计数器值,使用从相同的语句中产生的两个动作和使用一个共享动作会产生不同的行为。比如:

EXPECT_CALL(foo, DoThis())

  .WillRepeatedly(IncrementCounter(0));

EXPECT_CALL(foo, DoThat())

  .WillRepeatedly(IncrementCounter(0));

foo.DoThis();  // Returns 1.

foo.DoThis();  // Returns 2.

foo.DoThat();  // Returns 1 - Blah() uses a different

          // counter than Bar()'s.

    相较:

Action<int()> increment = IncrementCounter(0);

 

EXPECT_CALL(foo, DoThis())

  .WillRepeatedly(increment);

EXPECT_CALL(foo, DoThat())

  .WillRepeatedly(increment);

foo.DoThis();  // Returns 1.

foo.DoThis();  // Returns 2.

foo.DoThat();  // Returns 3 - the counter is shared.

  评论这张
 
阅读(2286)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017