Google Mock启蒙篇matcher详细尽说明

来源:互联网 发布:过期域名注册 编辑:程序博客网 时间:2024/05/22 16:04

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

Setting Expectations

    成功地使用 Mock 对象的关键是在它上面设置合适的期望。如果你设置的期望太过严格,你的测试可能会因为无关的改变而失败。如果你把期望设置的太过松驰, bugs可能会溜过去。而你需要的是你的测试可以刚好捕获你想要捕获的那一种 bug Google 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 的值是什么。 这个规则很容易记:

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

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

  如果有 n  WillOnce() ,和一个 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 次,并分别返回 100  200  300 

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

    它并不是依次返回 100  101  102... ,而是每次都返回 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, ... ,唉,你还是再想想吧!问题是,我们都说过了,期望是严格的。所以第 2 turtle.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 Dummies , Koala++/ 屈伟 如果在法律上拥有译作的版权,在此声明愿意自动放弃。
原创粉丝点击