当你写一个原型或者测试的时候,依赖整个object 是不可行和明智的。一个 mock object和 real object 有一样的接口(因此它能够像同一个使用),可是让你在运行时进行指定它应该如何被使用,它应当作什么(哪些方法应该被调用?以何种顺序?多少次?用什么参数?什么会被返回?)java
注意:很容易弄混 fake objects 和 mock objects。实际上fakes 和 mocks意味着不一样的事情在Test-Driven Development(TDD)社区:python
Fake object(伪对象) 有 工做实现,可是常常采起一些捷径(也许让操做不昂贵),这会使它们不适合生产。内存中的文件系统就是一个例子。git
Mocks 是有指望的预编程对象。由一些预期会被调用的 sepcification组成。程序员
若是这对你来讲太抽象的话,don't worry- 你须要记住最重要的就是mock 容许你使用它与你的代码进行交互。当你使用mocks的时候,你对于fakes和 mocks的区别就更清晰了,github
Google C++ Mocking Framework (or Google Mock for short) 是一个用来建立 mock 类库(叫“框架”是由于这样听起来更cool),就像 java 的 jMock and EasyMock。数据库
使用 Google Mock 包含如下三个基本步骤:编程
1. 使用一些简单的macros 来描述 你想mock的接口,这将扩展你的mock 类。网络
2. 用很直观的语法来描述一些mock对象的指望和行为。app
3. 练习使用mock 对象的 代码。任何 违反 expectation 的行为一出现就会被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:
一个设计工具,它可让你早日常常尝试你的接口设计。更多的迭代致使更好的设计!
一个测试工具,切断全部测试的外部依赖,探测你的模块和其余模块的交互!
开始吧
使用Google Mock很容易! 在你的C ++源文件中,只要#include“gtest / gtest.h”和“gmock / gmock.h”,你已经准备好了。
让咱们来看一个例子。假设你在开发一个图形程序依赖一个 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。传了哪些参数,以什么样的顺序。以这种方式编写的测试更强大,更容易读取和维护(测试的意图表示在代码中,而不是在一些二进制图像中)运行得多,快得多。
若是你幸运,你须要使用的mock已经被一些好的人实现。可是,你发现本身在写一个模拟class,放松- Google Mock将这个任务变成一个有趣的游戏!
使用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.
当你定义了 mock class,你得决定你把这些定义放到什么地方。有些人把它放在一个* _test.cc。当这些 mock对象是被一我的或者一个团队使用的时候,这样定义就很好。不然,当Foo的全部者改变它,你的测试可能会中断。 (你不能真正指望Foo的维护者修复使用Foo的每一个测试,你能吗?)
因此,经验法则是:若是你须要模拟Foo而且它由其余人拥有,在Foo的包中定义模拟类(更好的是,在一个测试子包中,你能够清楚地分离生产代码和测试实用程序),而且把它放在mock_foo.h。而后每一个人均可以从它们的测试引用mock_foo.h。若是Foo变化,只有一个MockFoo的副本要更改,只有依赖于更改的方法的测试须要修复。
另一种方法:你能够在Foo的顶部引入一个 薄层FooAdaptor ,并将代码引入这一新的接口。由于你拥有FooAdaptor,你能够更容易的吸取Foo的变化。虽然这是最初的工做,仔细选择适配器接口可使您的代码更容易编写和更加可读性,由于你能够选择FooAdaptor适合你的特定领域比Foo更好。
一旦你有了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容许你作更多的。
若是您要使用除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测试,请告诉咱们。
成功使用Mock Object的关键是对它设置正确的指望。 若是你设置的指望太严格,你的测试将失败做为无关的更改的结果。 若是你把它们设置得太松,错误能够经过。 你想作的只是正确的,使你的测试能够捕获到你想要捕获的那种错误。 Google Mock为您提供了必要的方法“恰到好处”。
在 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在消息中包括失败的指望的源文件位置,使调试更容易。
当一个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单位。
咱们能够在EXPECT_CALL()以后指定的第一个子句是Times()。咱们把它的参数称为基数,由于它告诉调用应该发生多少次。它容许咱们重复一个指望屡次,而不实际写屡次。更重要的是,一个基数能够是“模糊的”,就像一个匹配器。这容许用户准确地表达测试的意图。
一个有趣的特殊状况是当咱们说Times(0)。你可能已经猜到了 - 这意味着函数不该该使用给定的参数,并且Google Mock会在函数被(错误地)调用时报告一个Google测试失败。咱们已经看到AtLeast(n)做为模糊基数的一个例子。有关您可使用的内置基数列表,请参见CheatSheet。
Times()子句能够省略。若是你省略Times(),Google Mock会推断出你的基数。规则很容易记住:
快速测验:若是一个函数指望被调用两次,但实际上调用了四次,你认为会发生什么?
记住,一个模拟对象实际上没有工做实现? 咱们做为用户必须告诉它当一个方法被调用时该作什么。 这在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函数的默认操做。
到目前为止,咱们只列出了你有一个指望的例子。更现实地,你要指定对多个模拟方法的指望,这可能来自多个模拟对象。
默认状况下,当调用模拟方法时,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为何要以与预期相反的顺序搜寻匹配?缘由是,这容许用户在模拟对象的构造函数中设置默认指望,或测试夹具的设置阶段中设置默认指望,而后经过在测试体中写入更具体的指望来定制模拟。因此,若是你对同一个方法有两个指望,你想把一个具备更多的特定的匹配器放在另外一个以后,或更具体的规则将被更为通常的规则所覆盖。
默认状况下,即便未知足较早的指望,指望也能够匹配调用。换句话说,调用没必要按照指望被指定的顺序发生。
有时,您可能但愿全部预期的调用以严格的顺序发生。在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中找到。)
如今,让咱们作一个快速测验,看看你能够多好地使用这个模拟的东西。你会如何测试,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(); } }
模拟对象可能有不少方法,并非全部的都是那么有趣。例如,在一些测试中,咱们可能不关心GetX()和GetY()被调用多少次。
在Google Mock中,若是你对一个方法不感兴趣,只是不要说什么。若是调用此方法,您将在测试输出中看到一个警告,但它不会失败。
恭喜!您已经学会了足够的Google Mock开始使用它。如今,您可能想要加入googlemock讨论组,而且实际上使用Google Mock编写一些测试 - 这颇有趣。嘿,它甚至能够上瘾 - 你已经被警告。
而后,若是你想增长你的Mock商,你应该移动到 CookBook。您能够了解Google Mock的许多高级功能,并提升您的享受和测试幸福的水平。