当你写一个原型或者测试的时候,依赖整个object 是不可行和明智的。一个 mock object和 real object 有同样的接口(所以它可以像同一个使用),但是让你在运行时进行指定它应该如何被使用,它应当做什么(哪些方法应该被调用?以何种顺序?多少次?用什么参数?什么会被返回?)
注意:很容易弄混 fake objects 和 mock objects。实际上fakes 和 mocks意味着不同的事情在Test-Driven Development(TDD)社区:
Fake object(伪对象) 有 工作实现,但是经常采取一些捷径(也许让操作不昂贵),这会使它们不适合生产。内存中的文件系统就是一个例子。
Mocks 是有期望的预编程对象。由一些预期会被调用的 sepcification组成。
如果这对你来说太抽象的话,don't worry- 你需要记住最重要的就是mock 允许你使用它与你的代码进行交互。当你使用mocks的时候,你对于fakes和 mocks的区别就更清晰了,
Google C++ Mocking Framework (or Google Mock for short) 是一个用来创建 mock 类库(叫“框架”是因为这样听起来更cool),就像 java 的 jMock and EasyMock。
使用 Google Mock 包含以下三个基本步骤:
1. 使用一些简单的macros 来描述 你想mock的接口,这将扩展你的mock 类。
2. 用很直观的语法来描述一些mock对象的期望和行为。
3. 练习使用mock 对象的 代码。任何 违反 expectation 的行为一出现就会被Google Mock 捕获。
Why Google Mock?
为什么使用 Google Mock
虽然Mock Object 可以帮助你移除测试中不必要的依赖,并使它们快速可靠,但是在C ++中手动使用mock是很难的:
有些人不得不实现mocks。这个工作既乏味又容易出错。难怪有些人想要避免它。
手动写的mock的质量是不可靠的,无法预测的。你可能看过一些真正抛光过的,但是你也许会看到一些被匆忙砍掉有各种零时限制的。
你从一个mock 获得的知识不能使用到下一个mock上面。
相比之下,Java和Python程序员有一些精细的模拟框架,自动创建mock。因此,Mock是一种被证明是有效的技术,并在这些社区广泛采用的做法。拥有正确的工具绝对有所不同。
Google Mock旨在帮助C ++程序员。它的灵感来自jMock和EasyMock,但是设计时考虑了C ++的细节。它会帮助你的,如果你遇到以下问题:
你被不怎么好的设计所困扰,早知道应该做更多的原型设计的,但一切都太迟了,但是用C++进行原型设计速度会很慢。
您的测试很慢,因为它们依赖于太多的库或使用昂贵的资源(例如数据库)
你的测试是脆弱的,因为他们使用的一些资源是不可靠的(例如网络)
您想要测试代码如何处理失败(例如,文件校验和错误),但是不容易去制造这么一个失败。
你想确保你的当前模块和其他模块的交互是正确的,但是观察交互是很不容易的;因此你诉诸于观察行动结束时的副作用,这是最尴尬的
你想 mock out 你的 依赖,除了还mock还没有被实现;坦白的讲,你对那些手写的mock 不感冒
我们鼓励你像这样使用Google Mock:
一个设计工具,它可以让你早日经常尝试你的接口设计。更多的迭代导致更好的设计!
一个测试工具,切断所有测试的外部依赖,探测你的模块和其他模块的交互!
Getting Started
开始吧
使用Google Mock很容易! 在你的C ++源文件中,只要#include“gtest / gtest.h”和“gmock / gmock.h”,你已经准备好了。
A Case for Mock Turtles
让我们来看一个例子。假设你在开发一个图形程序依赖一个 LOGO-like的 API 来绘图。你该怎样测试它做了正确的事情呢?你可以运行它并且与一个golden screen snapshot进行比较,但是我承认:像这样测试是昂贵的并且很脆弱(如果你要更新到一个全新抗锯齿的图像该怎么办?你要更新你所有的golden images),如果你的所有测试都是这样的,这就很痛苦了。Fortunately,你学习到了Dependency Injection并且知道该作什么:不要让你的application 直接 调用 drawing API, 把API包在一个接口里(say, Turtle
) and code to that interface:
class Turtle { ... virtual ~Turtle() {} virtual void PenUp() = 0; virtual void PenDown() = 0; virtual void Forward(int distance) = 0; virtual void Turn(int degrees) = 0; virtual void GoTo(int x, int y) = 0; virtual int GetX() const = 0; virtual int GetY() const = 0; };
(注意,Turtle的析构函数必须是虚拟的,就像你打算继承的所有类的情况一样 - 否则当通过基类指针删除一个对象时,派生类的析构函数不会被调用,你会得到损坏的程序状态,如内存泄漏。)
您可以控制 使用PenUp()和PenDown()控制turtle的运动是否留下轨迹,并通过 Forward(),Turn()和GoTo()控制其运动。最后,GetX()和GetY()告诉你当前位置的turtle。
你的程序通常正常使用这个接口的实际实现。在测试中,你可以使用 实现的Mock来替换。这让你很容易的检查你程序你调用的 drawing primitives。传了哪些参数,以什么样的顺序。以这种方式编写的测试更强大,更容易读取和维护(测试的意图表示在代码中,而不是在一些二进制图像中)运行得多,快得多。
Writing the Mock Class
如果你幸运,你需要使用的mock已经被一些好的人实现。但是,你发现自己在写一个模拟class,放松- Google Mock将这个任务变成一个有趣的游戏!
How to Define It
使用Turtle接口作为示例,以下是您需要遵循的简单步骤:
1. MockTurtle继承Turtle类
2.使用Turtle的虚函数(虽然可以使用模板来模拟非虚方法 mock non-virtual methods using templates,但是它更多的涉及)。计算它有多少参数。
3. 在 public 区: section of the child class, write MOCK_METHODn();
(or MOCK_CONST_METHODn();
if you are mocking a const
method), where n
is the number of the arguments; if you counted wrong, shame on you, and a compiler error will tell you so.
4. 现在来到有趣的部分:你采取函数签名,剪切和粘贴函数名作为宏的第一个参数,留下的作为第二个参数(如果你好奇,这是类型的功能)
5. 重复,直到您要模拟的所有虚拟功能完成。
After the process, you should have something like:
#include "gmock/gmock.h" // Brings in Google Mock. class MockTurtle : public Turtle { public: ... MOCK_METHOD0(PenUp, void()); MOCK_METHOD0(PenDown, void()); MOCK_METHOD1(Forward, void(int distance)); MOCK_METHOD1(Turn, void(int degrees)); MOCK_METHOD2(GoTo, void(int x, int y)); MOCK_CONST_METHOD0(GetX, int()); MOCK_CONST_METHOD0(GetY, int()); };
您不需要在其他地方定义这些模拟方法 - MOCK_METHOD *宏将为您生成定义。 就是这么简单!
一旦你掌握了它,你可以快速的写出 mock class,以至于你的 source control system 都不能处理你的check-in 了
Tips: 如果 这对你来说工作量太大了,你可以在 Google Mock 的 scripts/generator/目录下面找到gmock_gen.py 工具。
Command-line 工具需要python2.4 安装。你只要给它一个 定义了抽象类的C++文件,它就会给你打印 其mock class。由于C++语言的复杂性,这个脚本可能不总是工作正常,但确实很有用,read the user documentation.
Where to Put It
当你定义了 mock class,你得决定你把这些定义放到什么地方。有些人把它放在一个* _test.cc。当这些 mock对象是被一个人或者一个团队使用的时候,这样定义就很好。否则,当Foo的所有者改变它,你的测试可能会中断。 (你不能真正期望Foo的维护者修复使用Foo的每个测试,你能吗?)
所以,经验法则是:如果你需要模拟Foo并且它由其他人拥有,在Foo的包中定义模拟类(更好的是,在一个测试子包中,你可以清楚地分离生产代码和测试实用程序),并且把它放在mock_foo.h。然后每个人都可以从它们的测试引用mock_foo.h。如果Foo变化,只有一个MockFoo的副本要更改,只有依赖于更改的方法的测试需要修复。
另外一种方法:你可以在Foo的顶部引入一个 薄层FooAdaptor ,并将代码引入这一新的接口。因为你拥有FooAdaptor,你可以更容易的吸收Foo的变化。虽然这是最初的工作,仔细选择适配器接口可以使您的代码更容易编写和更加可读性,因为你可以选择FooAdaptor适合你的特定领域比Foo更好。
Using Mocks in Tests
一旦你有了Mock 类,使用它非常容易。典型的工作流程如下:
1. 从测试命名空间导入Google Mock名称,以便您可以使用它们(每个文件只需执行一次。请记住,命名空间是一个好主意,有利于您的健康。)
2. 创建一些 mock对象
3.指定你对它们的期望(一个方法被调用多少次?有什么参数?它应该做什么等等)。
4.练习一些使用mock的代码; 可以使用Google Test断言检查结果。如果一个mock方法被调用超过预期或错误的参数,你会立即得到一个错误。
5. 当模mock destructed,Google Mock将自动检查是否满足了对其的所有期望
例子:
#include "path/to/mock-turtle.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::AtLeast; // #1 TEST(PainterTest, CanDrawSomething) { MockTurtle turtle; // #2 EXPECT_CALL(turtle, PenDown()) // #3 .Times(AtLeast(1)); Painter painter(&turtle); // #4 EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); } // #5 int main(int argc, char** argv) { // The following line must be executed to initialize Google Mock // (and Google Test) before running the tests. ::testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); }
正如你可能已经猜到的,这个测试检查PenDown()被调用至少一次。 如果painter对象没有调用此方法,您的测试将失败,并显示如下消息:
path/to/my_test.cc:119: Failure Actual function call count doesn't match this expectation: Actually: never called; Expected: called at least once.
提示1:如果从Emacs缓冲区运行测试,您可以在错误消息中显示的行号上按<Enter>,直接跳到失败的预期。
提示2:如果你的mock object 从来没有被删除,最终的验证不会发生。因此,当您在堆上分配mock时,在测试中使用堆泄漏检查器是个好主意。
重要提示:Google Mock 需要expectation 在mock 函数被调用之前就设置,否者 行为就是 未定义的(undefined)。尤其是,你不能交错 EXPECT_CALL()和调用函数
这意味着EXPECT_CALL()应该被读取为期望call将在未来发生,而不是call已经发生。为什么Google Mock会这样工作?
好的,事先指定期望允许Google Mock在上下文(堆栈跟踪等)仍然可用时立即报告违例。这使得调试更容易。
诚然,这个测试是设计的,没有做太多。不使用Google Mock,您也可以轻松实现相同的效果。然而,正如我们将很快揭示的,Google Mock允许你做更多的。
Using Google Mock with Any Testing Framework
如果您要使用除Google测试(例如CppUnit或CxxTest)之外的其他测试框架作为测试框架,只需将上一节中的main()函数更改为:
int main(int argc, char** argv) { // The following line causes Google Mock to throw an exception on failure, // which will be interpreted by your testing framework as a test failure. ::testing::GTEST_FLAG(throw_on_failure) = true; ::testing::InitGoogleMock(&argc, argv); ... whatever your testing framework requires ... }
这种方法有一个catch:它有时使Google Mock从一个模拟对象的析构器中抛出异常。对于某些编译器,这有时会导致测试程序崩溃。 你仍然可以注意到测试失败了,但它不是一个优雅的失败。
更好的解决方案是使用Google Test的事件侦听器APIevent listener API 来正确地向测试框架报告测试失败。 您需要实现事件侦听器接口的OnTestPartResult()方法,但它应该是直接的。
如果这证明是太多的工作,我们建议您坚持使用Google测试,它与Google Mock无缝地工作(实际上,它在技术上是Google Mock的一部分)。 如果您有某个原因无法使用Google测试,请告诉我们。
Setting Expectations
成功使用Mock Object的关键是对它设置正确的期望。 如果你设置的期望太严格,你的测试将失败作为无关的更改的结果。 如果你把它们设置得太松,错误可以通过。 你想做的只是正确的,使你的测试可以捕获到你想要捕获的那种错误。 Google Mock为您提供了必要的方法“恰到好处”。
General Syntax
在 Google Mock 中我们在 mock mecthod 中使用 EXPECT_CALL() 宏去设置expectation。 一般的语法是:
EXPECT_CALL(mock_object, method(matchers)) .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()方法将被调用五次,它将第一次返回100,第二次返回150,然后每次返回200。 有些人喜欢将这种语法风格称为域特定语言(DSL)。
注意:为什么我们使用宏来做到这一点? 它有两个目的:第一,它使预期容易识别(通过grep或由人类读者),其次它允许Google Mock在消息中包括失败的期望的源文件位置,使调试更容易。
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(_));
_是我们称为匹配器的实例.匹配器就像一个谓词,可以测试一个参数是否是我们期望的.你可以在EXPECT_CALL()里面使用一个匹配器来替换某一个参数。内置匹配器的列表可以在CheatSheet中找到。 例如,这里是Ge(大于或等于)匹配器:
using ::testing::Ge; ... EXPECT_CALL(turtle, Forward(Ge(100)));
这检查,turtle将被告知前进至少100单位。
Cardinalities: How Many Times Will It Be Called?
我们可以在EXPECT_CALL()之后指定的第一个子句是Times()。我们把它的参数称为基数,因为它告诉调用应该发生多少次。它允许我们重复一个期望多次,而不实际写多次。更重要的是,一个基数可以是“模糊的”,就像一个匹配器。这允许用户准确地表达测试的意图。
一个有趣的特殊情况是当我们说Times(0)。你可能已经猜到了 - 这意味着函数不应该使用给定的参数,而且Google Mock会在函数被(错误地)调用时报告一个Google测试失败。我们已经看到AtLeast(n)作为模糊基数的一个例子。有关您可以使用的内置基数列表,请参见CheatSheet。
Times()子句可以省略。如果你省略Times(),Google Mock会推断出你的基数。规则很容易记住:
- 如果WillOnce()和WillRepeatedly()都不在EXPECT_CALL()中,则推断的基数是Times(1)。
- 如果有n个WillOnce(),但没有WillRepeatedly(),其中n> = 1,基数是Times(n)
- 如果有n个WillOnce()和一个WillRepeatedly(),其中n> = 0,基数是Times(AtLeast(n))。
快速测验:如果一个函数期望被调用两次,但实际上调用了四次,你认为会发生什么?
Actions: What Should It Do?
记住,一个模拟对象实际上没有工作实现? 我们作为用户必须告诉它当一个方法被调用时该做什么。 这在Google Mock中很容易。
首先,如果一个模拟函数的返回类型是内置类型或指针,该函数有一个默认动作(一个void函数将返回,一个bool函数将返回false,其他函数将返回0)。
此外,在C ++ 11及以上版本中,返回类型为默认可构造(即具有默认构造函数)的模拟函数具有返回默认构造值的默认动作。 如果你不说什么,这个行为将被使用。
第二,如果模拟函数没有默认动作,或者默认动作不适合你,你可以使用一系列WillOnce()子句指定每次期望匹配时要采取的动作,后跟一个可选的WillRepeatedly ()。例如:
using ::testing::Return; ... EXPECT_CALL(turtle, GetX()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillOnce(Return(300));
这说明turtle.GetX()将被调用三次(Google Mock从我们写的WillOnce()子句中推断出了这一点,因为我们没有明确写入Times()),并且会返回100,200, 和300。
using ::testing::Return; ... EXPECT_CALL(turtle, GetY()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillRepeatedly(Return(300));
turtle.GetY()将被调用至少两次(Google Mock知道这一点,因为我们写了两个WillOnce()子句和一个WillRepeatedly(),没有明确的Times()),将第一次返回100,200 第二次,300从第三次开始。
当然,如果你明确写一个Times(),Google Mock不会试图推断cardinality(基数)本身。 如果您指定的数字大于WillOnce()子句,该怎么办? 好了,毕竟WillOnce()已用完,Google Mock每次都会为函数执行默认操作(除非你有WillRepeatedly()。)。
除了Return()之外,我们可以在WillOnce()中做什么? 您可以使用ReturnRef(variable)返回引用,或调用预定义函数等。
重要说明:EXPECT_CALL()语句只评估一次操作子句,即使操作可能执行多次。 因此,您必须小心副作用。 以下可能不会做你想要的:
int n = 100; EXPECT_CALL(turtle, GetX()) .Times(4) .WillRepeatedly(Return(n++));
不是连续返回100,101,102,...,这个mock函数将总是返回100,因为n ++只被计算一次。 类似地,当执行EXPECT_CALL()时,Return(new Foo)将创建一个新的Foo对象,并且每次都返回相同的指针。 如果你想要每次都发生副作用,你需要定义一个自定义动作,我们将在 CookBook中教授。
另一个测验! 你认为以下是什么意思?
using ::testing::Return; ... EXPECT_CALL(turtle, GetY()) .Times(4) .WillOnce(Return(100));
显然turtle.GetY()被期望调用四次。但如果你认为它会每次返回100,三思而后行!请记住,每次调用函数时都将使用一个WillOnce()子句,然后执行默认操作。所以正确的答案是turtle.GetY()将第一次返回100,但从第二次返回0,因为返回0是int函数的默认操作。
Using Multiple Expectations
到目前为止,我们只列出了你有一个期望的例子。更现实地,你要指定对多个模拟方法的期望,这可能来自多个模拟对象。
默认情况下,当调用模拟方法时,Google Mock将按照它们定义的相反顺序搜索期望值,并在找到与参数匹配的活动期望时停止(您可以将其视为“新规则覆盖旧的规则“)。如果匹配期望不能再接受任何调用,您将得到一个上限违反的失败。这里有一个例子:
using ::testing::_; ... EXPECT_CALL(turtle, Forward(_)); // #1 EXPECT_CALL(turtle, Forward(10)) // #2 .Times(2);
如果Forward(10)在一行中被调用三次,第三次它将是一个错误,因为最后的匹配期望(#2)已经饱和。然而,如果第三个Forward(10)被Forward(20)替换,则它将是OK,因为现在#1将是匹配期望。
附注:Google 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的对象,其范围中的所有期望都被放入序列中,并且必须按顺序发生。因为我们只是依靠这个对象的构造函数和析构函数做实际的工作,它的名字真的无关紧要。
在这个例子中,我们测试Foo()按照书写的顺序调用三个期望函数。如果调用是无序的,它将是一个错误。
如果你关心一些呼叫的相对顺序,但不是所有的呼叫,你能指定一个任意的部分顺序吗?答案是...是的!如果你不耐烦,细节可以在CookBook中找到。)
All Expectations Are Sticky (Unless Said Otherwise)
所有期望都是粘滞的(Sticky)(除非另有说明)
现在,让我们做一个快速测验,看看你可以多好地使用这个模拟的东西。你会如何测试,turtle被要求去原点两次(你想忽略任何其他指令)?
在你提出了你的答案,看看我们的比较的笔记(自己先解决 - 不要欺骗!):
using ::testing::_; ... EXPECT_CALL(turtle, GoTo(_, _)) // #1 .Times(AnyNumber()); EXPECT_CALL(turtle, GoTo(0, 0)) // #2 .Times(2);
假设turtle.GoTo(0,0)被调用了三次。 第三次,Google Mock将看到参数匹配期望#2(记住,我们总是选择最后一个匹配期望)。 现在,由于我们说应该只有两个这样的调用,Google Mock会立即报告错误。 这基本上是我们在上面“使用多个期望”部分中告诉你的。
这个例子表明,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,...,连续,三思而后行! 问题是,正如我们所说,期望是粘性的。 所以,第二次turtle.GetX()被调用,最后(最新)EXPECT_CALL()语句将匹配,并将立即导致“上限超过(upper bound exceeded)”错误 - 这段代码不是很有用!
一个正确的说法是turtle.GetX()将返回10,20,30,...,是明确说,期望是不粘的。 换句话说,他们应该在饱和后尽快退休:
using ::testing::Return; ... for (int i = n; i > 0; i--) { EXPECT_CALL(turtle, GetX()) .WillOnce(Return(10*i)) .RetiresOnSaturation(); }
而且,有一个更好的方法:在这种情况下,我们期望调用发生在一个特定的顺序,我们排列动作来匹配顺序。 由于顺序在这里很重要,我们应该显示的使用一个顺序:
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
模拟对象可能有很多方法,并不是所有的都是那么有趣。例如,在一些测试中,我们可能不关心GetX()和GetY()被调用多少次。
在Google Mock中,如果你对一个方法不感兴趣,只是不要说什么。如果调用此方法,您将在测试输出中看到一个警告,但它不会失败。
What Now?
恭喜!您已经学会了足够的Google Mock开始使用它。现在,您可能想要加入googlemock讨论组,并且实际上使用Google Mock编写一些测试 - 这很有趣。嘿,它甚至可以上瘾 - 你已经被警告。
然后,如果你想增加你的Mock商,你应该移动到 CookBook。您可以了解Google Mock的许多高级功能,并提高您的享受和测试幸福的水平。