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

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

Google Mock启蒙篇 [2] (Google C++ Mocking Framework for Dummies 翻译)  

2011-11-22 22:34:58|  分类: C++ |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Setting Expectations

    成功地使用Mock对象的关键是在它上面设置合适的期望。如果你设置的期望太过严格,你的测试可能会因为无关的改变而失败。如果你把期望设置的太过松驰,bugs可能会溜过去。而你需要的是你的测试可以刚好捕获你想要捕获的那一种bugGoogle Mock提供了一些方法可以让你的测试尺度刚好( just right )

General Syntax

    Goolge Mock中,我们用EXPECT_CALL()宏来设置一个Mock函数上的期望。一般语法是:

EXPECT_CALL(mock_object, method(matchers))< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />

    .Times(cardinality)

    .WillOnce(action)

.WillRepeatedly(action);

这个宏有两个参数:第一个是Mock对象,第二个参数是函数和它的参数。注意两个参数是用逗号( , )分隔的,而不是句号( . )

这个宏可以跟一些可选子句,这些子句可以提供关于期望更多的信息。我们将会在下面的小节中介绍每个子句有什么意义。

这些语法设计的一个目的是让它们读起来像是英语。比如你可能会直接猜出下面的代码是有什么含义

using ::testing::Return;...

EXPECT_CALL(turtle, GetX())

    .Times(5)

    .WillOnce(Return(100))

    .WillOnce(Return(150))

.WillRepeatedly(Return(200));

公布答案,turtle对象的GetX()方法会被调用5次,它第一次返回100,第二次返回150,然后每次返回200。许多人喜欢称这种语法方式为特定领域语言( Domain-Specific Language (DSL) )

注意:为什么我们要用宏来实现呢?有两个原因:第一,它让期望更容易被认出来( 无论是grep还是人去阅读 ),第二,它允许Google Mock可以得到失败期望在源文件的位置,从而使Debug更容易。

Matchers: What Arguments Do We Expect?

    当一个Mock函数需要带参数时,我们必须指定我们期望的参数的是什么;比如:

// Expects the turtle to move forward by 100 units.

EXPECT_CALL(turtle, Forward(100));

    有时你可能不想指定的太精确( 还记得前面测试不应太严格吗?指定的太精确会导致测试健壮性不足,并影响测试的本意。所以我们鼓励你只指定那些必须要指定的参数,不要多,也不要少 )。如果你只关心Forward是否会被调用,而不关心它用什么参数,你可以写_作为参数,它的意义是“任意”参数。

using ::testing::_;

...

// Expects the turtle to move forward.

EXPECT_CALL(turtle, Forward(_));

    _是我们称为Matchers的一个例子,一个matcher是像一个断言,它可测试一个参数是否是我们期望的。你可用在EXPECT_CALL()中任何写函数参数期望的地方用matcher

    一个内置的matchers可以在CheatSheet中找到,比如,下面是Ge( greater than or equal ) matcher的应用。

using ::testing::Ge;...

EXPECT_CALL(turtle, Forward(Ge(100)));

    这个测试是检查turtle是否被告知要至少前进至少100个单位。

Cardinalities: How Many Times Will It Be Called?

    EXPECT_CALL()之后第一个我们可以指定的子句是Times()。我们称Times的参数为cardinality,因为它是指这个函数应该被调用多少次Times可以让我们指定一个期望多次,而不用去写一次次地写这个期望。更重要的是,cardinality可以是“模糊”的,就像matcher一样。它可以让测试者更准确地表达他测试的目的。

    一个有趣的特例是我们指定Times(0)。你也许已经猜到了,它是指函数在指定参数下不应该被调用,如果这个函数被调用了,Google Mock会报告一个Google Test失败。

    我们已经见过AtLeast(n)这个模糊cardinalities的例子了。你可以在CheatSheet中找一个内置cardinalities列表。

    Times()子句可以省略。如果你省略Times()Google Mock会推断出cardinality的值是什么。这个规则很容易记:

l  如果在EXPECT_CALL既没有WillOnce()也没有WillRepeatedly(),那推断出的cardinality就是Times(1)

l  如果有nWillOnce(),但没有WillRepeatedl(),其中n >= 1,那么cardinality就是Times(n)

l  如果有nWillOnce(),和一个WillRepeatedly(),其中n >= 0,那么cardinality就是Times(AtLeast(n))

小测试:如果一个函数期望被调用2次,但被调用了4次,你认为会发生什么呢?

Actions: What Should It Do?

    请记住一个Mock对象其实是没有实现的。是我们这些用户去告诉它当一个函数被调用时它应该做什么。这在Google Mock中是很简单的。

    首先,如果Mock函数的返回类型是一个指针或是内置类型,那这个函数是有默认行为( 一个void函数直接返回,bool函数返回false,其它函数返回0 )。如果你不想改变它,那这种行为就会被应用。

    其次,如果一个Mock函数没有默认行为,或默认行为不适合你,你可以用WillOnce来指定每一次的返回值是什么,最后可以选用WillRepeatedly来结束。比如:

using ::testing::Return;...

EXPECT_CALL(turtle, GetX())

    .WillOnce(Return(100))

    .WillOnce(Return(200))

 .WillOnce(Return(300));

上面的意思是turtle.GetX()会被调用恰好3次,并分别返回100200300

using ::testing::Return;...

EXPECT_CALL(turtle, GetY())

.WillOnce(Return(100))

   .WillOnce(Return(200))

.WillRepeatedly(Return(300));

上面的意思是指turtle.GetY()至少被调用2次,第一次返回100,第二次返回200,从第三次以后都返回300

当然,你如果你明确写上Times()Google Mock不会去推断cardinality了。如果你指定的cardinality大于WillOnce()子句的个数时会发生什么呢?嗯,当WillOnce()用完了之后,Google Mock会每次对函数采用默认行为。

    我们在WillOnce()里除了写Return()我们还能做些什么呢?你可以用ReturnRef( variable ),或是调用一个预先定义好的函数,自己在Others中找吧。

重要提示:EXPECT_CALL()只对行为子句求一次值,尽管这个行为可能出现很多次。所以你必须小心这种副作用。下面的代码的结果可能与你想的不太一样。

int n = 100;

EXPECT_CALL(turtle, GetX())

.Times(4)

.WillRepeatedly(Return(n++));

    它并不是依次返回100101102...,而是每次都返回100,因为n++只会被求一次值。类似的,Return(new Foo)EXPECT_CALL()求值时只会创建一个Foo对象,所以它会每次都返回相同的指针。如果你希望每次都看到不同的结果,你需要定义一个自定义行为,我们将在CookBook中指导你。

    现在又是一个小测验的时候了!你认为下面的代码是什么意思?

using ::testing::Return;...

EXPECT_CALL(turtle, GetY())

.Times(4)

.WillOnce(Return(100));

    显然,turtle.Get()期望被调用4次。但如果你认为它每次都会返回100,那你就要再考虑一下了!记住,每次调用都会消耗一个WillOnce()子句,消耗完之后,就会使用默认行为。所以正确的答案是turtle.GetY()第一次返回100,以后每次都返回0,因为0是默认行为的返回值。

Using Multiple Expectations

    至今为止,我们只展示了如何使用单个期望。但是在现实中,你可能想指定来自不同Mock对象的Mock函数上的期望。

    默认情况下,当一个Mock函数被调用时,Google Mock会通过定义顺序的逆序去查找期望,当找到一个与参数匹配的有效的期望时就停下来( 你可以把这个它想成是“老的规则覆盖新的规则“ )。如果匹配的期望不能再接受更多的调用时,你就会收到一个超出上界的失败,下面是一个例子:

using ::testing::_;...

EXPECT_CALL(turtle, Forward(_));  // #1

EXPECT_CALL(turtle, Forward(10))  // #2

    .Times(2);

如果Forward(10)被连续调用3次,第3次调用它会报出一个错误,因为最后一个匹配期望(#2)已经饱和了。但是如果第3次的Forward(10)替换为Forward(20),那它就不会报错,因数现在#1将会是匹配的期望了。

边注:为什么Google Mock会以逆序去匹配期望呢?原因是为了可以让用户开始时使用Mock对象的默认行为,或是一些比较松驰的匹配条件,然后写一些更明确的期望。所以,如果你在同一个函数上有两个期望,你当然是想先匹配更明确的期望,然后再匹配其它的,或是可以说明确的规则会隐藏更宽泛的规则。

Ordered vs Unordered Calls

    默认情况下,即使是在前一个期望没有被匹配的情况下,一个期望仍然可以被匹配。换句话说,调用的匹配顺序不会按照期望指定的顺序去匹配。

    有时,你可能想让所有的期望调用都以一个严格的顺序来匹配,这在Google Mock中是很容易的:

using ::testing::InSequence;...

TEST(FooTest, DrawsLineSegment) {

  ...

  {

    InSequence dummy;

 

    EXPECT_CALL(turtle, PenDown());

    EXPECT_CALL(turtle, Forward(100));

    EXPECT_CALL(turtle, PenUp());

  }

  Foo();

}

    创建InSequence的一个对象后,在这个对象作用域中的期望都会以顺序存放,并要求调用以这个顺序匹配。因为我们只是依赖这个对象的构造函数和析构函数来完成任务,所以对象的名字并不重要。

( 如果你只是关心某些调用的相对顺序,而不是所有调用的顺序?可以指定一个任意的相对顺序吗?答案是...可以!如果你比较心急,你可以在CookBook中找到相关的细节。)

All Expectations Are Sticky (Unless Said Otherwise)

    现在让我们做一个小测验,看你掌握Mock到什么程度了。你如何测试turtle恰好经过原点两次?

    当你想出你的解法之后,看一下我们的答案比较一下( 先自己想,别作弊 )

using ::testing::_;...

EXPECT_CALL(turtle, GoTo(_, _))  // #1

    .Times(AnyNumber());

EXPECT_CALL(turtle, GoTo(0, 0))  // #2

.Times(2);

假设turtle.GoTo(0,0)被调用了3次。在第3次,Google Mock会找到参数匹配期望#2。因为我们想要的是恰好经过原点两次,所以Google Mock会立即报告一个错误。上面的内容其实就是我们在“Using Multiple Expectations”中说过的。

    上面的例子说明了Google Mock默认情况下期望是严格的,即是指期望在达到它们指定的调用次数上界后仍然是有效的。这是一个很重要的规则,因为它影响着指定的意义,而且这种规则与许多别的Mock框架中是不一样( 我们为什么会设计的不一样?因为我们认为我们的规则会使一般的用例更容易表达和理解 )

    简单?让我看一下你是不是真懂了:下面的代码是什么意思:

using ::testing::Return;

...

for (int i = n; i > 0; i--) {

  EXPECT_CALL(turtle, GetX())

      .WillOnce(Return(10*i));

}

    如果你认为turtle.GetX()会被调用n次,并依次返回10, 20, 30, ...,唉,你还是再想想吧!问题是,我们都说过了,期望是严格的。所以第2turtle.GetX()被调用时,最后一个EXPECT_CALL()会被匹配,所以马上会引起“超出上界”的错误。上面的代码其实没什么用途。

    一个正确表达turtle.GetX()返回10, 20, 30,...,的方法是明确地说明期望不是严格的。换句话说,在期望饱和之后就失效

using ::testing::Return;

...

for (int i = n; i > 0; i--) {

  EXPECT_CALL(turtle, GetX())

    .WillOnce(Return(10*i))

    .RetiresOnSaturation();

}

    并且,有一个更好的解决方法,在这个例子中,我们期望调用以特定顺序执行。因为顺序是一个重要的因素,我们应该用InSequence明确地表达出顺序:

using ::testing::InSequence;

using ::testing::Return;

...

{

  InSequence s;

 

  for (int i = 1; i <= n; i++) {

    EXPECT_CALL(turtle, GetX())

        .WillOnce(Return(10*i))

        .RetiresOnSaturation();

  }

}

    顺便说一下,另一个期望可能严格的情况是当它在一个顺序中,当这个期望饱和后,它就自动失效,从而让下一个期望有效。

Uninteresting Calls

    一个Mock对象可能有很多函数,但并不是所有的函数你都关心。比如,在一些测试中,你可能不关心GetX()GetY()被调用多少次。

    Google Mock中,你如果不关心一个函数,很简单,你什么也不写就可以了。如果这个函数的调用发生了,你会看到测试输出一个警告,但它不会是一个失败。

What Now?

    恭喜!你已经学习了足够的Google Mock的知识了,你可以开始使用它了。现在你也许想加入googlemock讨论组,并开始真正地用Google Mock开始写一些测试——它是很有意思的,嗨,这可能是会上瘾的,我可是警告过你了喔!

    如果你想提高你的Mock等级,你可以移步至CookBook。你可以在那学习更多的Google Mock高级特性——并提高你的幸福指数和测试快乐级别。

Copyright notice

    所有的内容全部翻译自Google的文档Google C++ Mocking Framework for DummiesKoala++/屈伟 如果在法律上拥有译作的版权,在此声明愿意自动放弃。
  评论这张
 
阅读(4716)| 评论(0)
推荐 转载

历史上的今天

评论

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

页脚

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