ForDummies.md
( 注意: 如果遇到不了解的编译器错误,请务必咨询 Google Mock Doctor.)
什么是Google C ++模拟框架?
当您编写原型或测试时,通常完全依靠真实对象是不可行或不明智的. 一个 mock object 实现与真实对象相同的接口(因此可以用作一个对象),但是可以让您在运行时指定如何使用它以及应该执行什么操作(将调用哪些方法?按什么顺序?多少次?带有哪些参数?它们将返回什么?等等)。
注意: 术语“fake objects”与模拟对象很容易混淆。假的(Fake)和模拟的(Mocks)在测试驱动开发(TDD)社区中实际上意味着截然不同的事物:
- Fake 对象具有有效的实现,但是通常采取一些捷径(也许使操作更便宜),这使其不适用于生产。内存文件系统将是一个伪造的例子。
- Mocks 是使用_expectations_预先编程的对象,这些对象形成了预期会收到的调用的规范
如果这一切对您来说都太抽象了,请不要担心-要记住的最重要的一点是,模拟允许您检查自身与使用它的代码之间的_interaction_。一旦开始使用模拟,伪造品与模拟品之间的区别将变得更加清晰。
Google C++ Mocking Framework (或者 Google Mock 简称) 是一个库 (有时我们也称其为“框架”以使其听起来很酷) 用于创建模拟类并使用它们. 它对C ++有作用 jMock 和EasyMock 是给java的
使用Google Mock涉及三个基本步骤:
- 使用一些简单的宏来描述您要模拟的接口,它们将扩展为模拟类的实现;
- 创建一些模拟对象并使用直观语法指定其期望值和行为;
- 使用模拟对象的练习代码。 Google Mock一旦发现,就会立即发现任何违反期望的情况。
为什么选择Google Mock?
虽然模拟对象可以帮助您删除测试中不必要的依赖关系并使它们快速可靠,但是在C ++中手动使用模拟功能是_hard_:
- 有人必须实现模拟。这项工作通常很乏味且容易出错。难怪人们要走很远的距离来避免它.
- 那些手动编写的模拟的质量有点不可预测。您可能会看到一些非常精美的内容,但是您可能还会看到一些被匆忙入侵并受到各种临时限制的内容.
- 您使用一种模拟所获得的效果不会转移到下一用例.
相比之下,Java和Python程序员拥有一些不错的模拟框架,可以自动创建模拟。结果,mock是一种行之有效的技术,并且在那些社区中被广泛采用。拥有正确的工具绝对会有所作为。
Google Mock旨在帮助C ++程序员. 它的灵感来自 jMock and EasyMock, 但在设计时要考虑C ++的具体细节。如果以下任何一个问题困扰您,它将帮助你:
- 您会陷入最优设计的困境,并希望在为时已晚之前进行更多的原型设计,但是C ++中的原型设计绝不是“rapid”的。
- 您的测试很慢,因为它们依赖太多的库或使用昂贵的资源(例如数据库)。
- 您的测试很脆弱,因为它们使用的某些资源不可靠(例如网络)。
- 您想测试代码如何处理失败(例如文件校验和错误),但是导致失败并不容易。
- 您需要确保您的模块与其他模块以正确的方式进行交互,但是很难观察到这种交互。因此,您必须在操作结束时采取观察副作用的方法,这充其量是尴尬的。
- 您要“mock out”您的依赖项,只是它们还没有模拟实现。坦率地说,您对那些手写的模拟并不感到兴奋。
我们建议您将Google Mock用作:
- _design_工具,因为它可以让您尽早且经常地进行界面设计试验。迭代次数越多,设计越好!
- 一个_testing_工具,以减少测试的出站依赖关系并探查模块与其协作者之间的交互。
入门
使用Google Mock很简单!在您的C ++源文件中, just #include
"gtest/gtest.h"
and "gmock/gmock.h"
, and 你准备好了。
模拟Turtles 用例#
让我们来看一个例子。假设您正在开发一个图形程序,该程序依赖于类似于LOGO的API进行绘制。您将如何测试它是否正确?好吧,您可以运行它并将其与黄金屏幕快照进行比较,但让我们承认:像这样的测试运行起来既昂贵又脆弱(如果您只是升级到具有更好抗锯齿功能的闪亮新显卡,该怎么办?您必须更新所有黄金图像。)。如果您的所有测试都这样,那将太痛苦了。幸运的是,您了解了依赖注入,并且知道正确的做法:与其让您的应用程序直接与绘图API对话,不如将API封装在一个接口(例如,“ Turtle”)中,并将代码包装到该接口中:
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()
and PenDown()
, 并使用控制其运动 Forward()
, Turn()
, and GoTo()
. 最后, GetX()
and GetY()
告诉你乌龟的当前位置.
您的程序通常将使用此接口的实际实现。在测试中,您可以改用模拟实现。这使您可以轻松检查程序正在调用哪些图形基元,具有哪些参数以及以什么顺序。用这种方式编写的测试更加健壮(它们不会中断,因为您的新计算机在抗锯齿方面有所不同),更易于阅读和维护(测试的意图在代码中表达,而不是在某些二进制映像中),并且跑得快得多。
编写模拟类
如果幸运的话,一些好人已经实现了您需要使用的模拟。但是,如果您发现自己可以编写模拟类,请放轻松-Google Mock可以将此任务变成一个有趣的游戏! (好吧,差不多)
如何定义
以Turtle
界面为例,以下是您需要遵循的简单步骤:
- 从
Turtle
派生一个类MockTurtle
。 - Take a virtual function of
Turtle
(虽然有可能 使用模板模拟非虚拟方法, 它涉及更多). 计算它有多少个参数. - 在
public:
部分, 写MOCK_METHODn();
(或者MOCK_CONST_METHODn();
用来模拟const
方法),n
表示有几个参数;如果您算错了,可耻的是,编译器错误将告诉您。 - 现在有趣的部分开始了:您可以使用函数签名,将_function name_剪切并粘贴为宏的_first_参数,然后将剩下的保留为_second_参数(如果您好奇的话,这就是function_的type_)。
- 重复直到要模拟的所有虚函数完成。
完成此过程后,您应该具有以下内容:
#include "gmock/gmock.h" // 引入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*
宏将生成定义这些方法,就这么简单!一旦掌握了这些要点,就可以比源代码控制系统处理签入的速度快出模拟类。
**Tip:**即使这对您来说太多了,您也会发现
gmock_gen.py
Google Mock的工具 scripts/generator/
目录(由 cppclean 项目) 有用. 此命令行
工具要求您已安装Python 2.4。您给它一个C ++文件和其中定义的抽象类的名称,
它将为您打印模拟类的定义. 因为C ++语言的复杂性,此脚本可能并不总是有效,但这样做时可能会非常方便。有关更多详细信息,请阅读 user documentation.
放在哪里
定义模拟类时,需要确定将其定义放在何处。有人把它放在一个*_test.cc
. 模拟接口时很好 (看, Foo
)由同一个人或团队拥有。除此以外,当拥有者 Foo
改变它, 您的测试可能会失败。 (你真的不能期待Foo
维护人员修复使用的每个测试 Foo
, 你可以吗?)
因此,经验法则是:如果您需要模拟 Foo
归他人所有, 在中定义模拟类Foo
包(更好,在 testing
子程序包,以便您可以清楚地区分生产代码和测试实用程序), 放在一个 mock_foo.h
.那大家可以参考 mock_foo.h
从他们的测试. 如果 Foo
不断变化, 只有一份MockFoo
改变,并且仅依赖于已更改方法的测试需要修复。
另一种方法:您可以引入一个适配层 FooAdaptor
在之上Foo
并编码到这个新界面. 既然你拥有 FooAdaptor
,您可以更轻松地吸收Foo
中的更改。尽管起初需要做更多工作,但仔细选择适配器接口可以使您的代码更易于编写和更具可读性(从长远来看是一个双赢),因为您可以选择FooAdaptor
来比Foo
更好地适合您的特定域。
在测试中使用模拟
一旦有了模拟类,使用它就很容易。典型的工作流程是:
- 从
testing
名称空间导入Google Mock
名称,这样您就可以不加限定地使用它们(每个文件只需要执行一次。请记住,名称空间是个好主意,对您的健康有益。)。 - 创建一些模拟对象。
- 指定您对它们的期望(一个方法将被调用多少次?带有什么参数?应该做什么?等等)。
- 练习一些使用模拟的代码; (可选)使用Google Test断言检查结果。如果模拟方法的调用超出预期或参数错误,您将立即收到错误消息。
- 当模拟被销毁后,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.
Tip 1: 如果从Emacs缓冲区运行测试,则可以在错误消息中显示的行号上按<Enter>
,以跳至失败的期望值。
Tip 2: 如果您的模拟对象从未删除,则最终验证不会发生。因此,当您在堆上分配模拟时,最好在测试中使用堆泄漏检查器。
重要的提示: Google Mock要求在调用模拟函数之前设置期望,否则行为未定义。特别是,您不得交错EXPECT_CALL()
和调用模拟函数。
这意味着应将 EXPECT_CALL()
读取为“将来会发生调用”,而不是已发生调用。为什么Google Mock
会这样工作?好吧,如果上下文(堆栈跟踪等)仍然可用,则预先指定期望值可使Google Mock
在违规发生时立即报告。这使调试更加容易。
诚然,该测试是人为设计的,不会做太多事情。您无需使用Google Mock
即可轻松实现相同的效果。但是,正如我们将很快揭示的那样,Google Mock
允许您使用该模拟功能做更多的事情。
结合使用Google Mock和任何测试框架
如果您想使用Google Test以外的其他产品(e.g. CppUnit or
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 ...
}
这种方法有一个陷阱:它使Google Mock抛出异常
有时来自模拟对象的析构函数。对于某些编译器,这
有时会导致测试程序崩溃。您仍然可以
请注意,测试已失败,但这不是正常的失败。
更好的解决方案是使用Google Test
event listener API
向测试框架正确报告测试失败。你需要实现事件监听器接口的 OnTestPartResult()
方法,但是应该很简单。
如果结果证明工作量过多,建议您坚持Google Test,它与Google Mock无缝兼容(实际上,它是
技术上是Google Mock的一部分。)。如果您有理由无法使用Google测试,请告诉我们。
设定期望
成功使用模拟对象的关键是在其上设置正确的期望值。如果您对期望的设置太严格,则测试将因不相关的更改而失败。如果将它们设置得太松,则可能会漏入错误。您想正确地做它,以便您的测试可以准确地捕获您打算捕获的错误类型。 Google Mock
为您“做到正确”提供了必要的手段。
一般语法
在Google Mock中,我们使用 EXPECT_CALL()
宏来设置模拟方法的期望值。通用语法为:
EXPECT_CALL(mock_object, method(matchers))
.Times(cardinality)
.WillOnce(action)
.WillRepeatedly(action);
宏有两个参数:首先是模拟对象,然后是方法及其参数,请注意,两者之间用逗号(,
)隔开,而不是句点(.
)。 (为什么要使用逗号?答案是出于技术原因而有必要。)
宏后可以跟一些可选的_clauses_,它们提供有关期望的更多信息。在接下来的部分中,我们将讨论每个子句的工作方式。
此语法旨在使期望读起来像英语。例如,您可能会猜到
using ::testing::Return;...
EXPECT_CALL(turtle, GetX())
.Times(5)
.WillOnce(Return(100))
.WillOnce(Return(150))
.WillRepeatedly(Return(200));
表示turtle
对象的GetX()
方法将被调用五次,它将第一次返回100,第二次返回150,然后每次返回200。有些人喜欢将这种语法称为领域特定语言(DSL)。
Note: 为什么我们使用宏来执行此操作?它有两个目的:首先,它使期望值易于识别(通过 grep
或人类读者),其次,它允许Google Mock
在消息中包含失败期望值的源文件位置,从而使调试更加容易。
匹配者:我们期望什么参数?
当模拟函数接受参数时,我们必须指定期望的参数。例如:
// 期望乌龟向前移动100个单位。
EXPECT_CALL(turtle, Forward(100));
有时您可能不想太具体(记住谈论测试过于严格吗?过规范会导致测试变脆,并掩盖了测试的意图。因此,我们鼓励您仅指定必要的内容,不要多也不少。)如果您想检查Forward()
是否会被调用,但对它的实际参数不感兴趣,请写成Forward()
作为参数,这意味着“一切顺利”:
using ::testing::_;
...
// 期待乌龟前进。
EXPECT_CALL(turtle, Forward(_));
_
是我们称为“匹配器matchers
”的实例。匹配器就像谓词一样,可以测试参数是否符合我们的期望。您可以在 EXPECT_CALL()
中使用匹配器,只要需要使用函数参数即可。
内置匹配器列表可以在 CheatSheet. 例如,这是 Ge
(大于或等于) matcher:
using ::testing::Ge;...
EXPECT_CALL(turtle, Forward(Ge(100)));
这样可以检查龟至少要向前前进100个单位。
基数:将被调用多少次?
我们可以在EXPECT_CALL()
之后指定的第一个参数是Times()
。我们称其参数为基数cardinality
,因为它告诉了调用应该发生多少次。它使我们可以重复多次期望,而不必实际编写多次。更重要的是,基数可以是模糊的,就像匹配器一样。这使用户可以准确表达测试的意图。
一个有趣的特殊情况是当我们说Times(0)
时。您可能已经猜到了-这意味着根本不应该使用给定的参数调用该函数,并且无论何时(错误地)调用该函数,Google Mock
都将报告Google Test
失败。
前面我们已经将 AtLeast(n)
作为模糊基数的示例。有关您可以使用的内置基数的列表,请参见 CheatSheet.
Times()
子句可以省略。如果省略Times()
,Google Mock将为您推断基数。规则很容易记住:
- 如果
WillOnce()
和WillOnce()
都不在EXPECT_CALL()
中,则推断的基数为Times(1)
。 - 如果存
n
个在WillOnce()
而没有WillRepeatedly()
,其中n
> = 1,则基数为Times(n)
。 - 如果有
n
个WillOnce()
和一个WillRepeatedly()
,其中n
> = 0,则基数为Times(AtLeast(n))
.
快速测验: 如果一个函数被调用两次但实际上被调用四次,您认为会发生什么?
行动:该怎么办?
还记得模拟对象实际上没有有效的实现吗?作为用户,我们必须告诉它在调用方法时该怎么做。在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
将不会尝试推断基数本身。如果您指定的数字大于WillOnce()
子句怎么办?好吧,在所有WillOnce()
用完之后,Google Mock每次都会对该函数执行_default_操作(当然,除非您有 WillRepeatedly()
.。)
除了 Return()
之外,我们还能在 WillOnce()
内部做什么?您可以使用ReturnRef(variable)
返回引用,或调用预定义的函数, CheatSheet.md.
重要说明: 即使动作可以执行多次,EXPECT_CALL()
语句仅评估动作子句一次。因此,您必须注意副作用。以下内容可能无法满足您的要求:
int n = 100;
EXPECT_CALL(turtle, GetX())
.Times(4)
.WillRepeatedly(Return(n++));
代替连续返回100、101、102,…,此模拟函数将始终返回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)
,那就可以了,因为现在#1将是匹配的期望值。
附注: 为什么Google Mock会按期望的_reverse_顺序搜索匹配项?原因是,这允许用户在模拟对象的构造函数或测试装置的设置阶段中设置默认期望,然后通过在测试主体中编写更具体的期望来自定义模拟。因此,如果您对同一方法有两个期望,则希望将一个具有更具体的匹配项的对象放在另一个之后,否则,更具体的规则将被后面紧随其后的更笼统的阴影所笼罩。
有序与无序调用
默认情况下,即使未满足先前的期望,期望也可以匹配呼叫。换句话说,呼叫不必按期望的指定顺序进行。
有时,您可能希望所有预期的调用都按照严格的顺序进行。在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
类型的对象,将其范围内的所有期望都放入_sequence_
中,并且必须_sequentially_
发生。由于我们只是依靠该对象的构造函数和析构函数来完成实际工作,因此它的名称确实无关紧要。
在这个例子中,我们测试Foo()
按照编写的顺序调用了三个期望的函数。如果呼叫混乱,将是一个错误。
(如果您关心某些呼叫的相对顺序,但不是全部呼叫,该怎么办?您可以指定任意部分呼叫的顺序吗?答案是…是!如果您不耐烦,可以在CookBook.)
所有期望都是粘性的(除非另有说明)##
现在,让我们做一个快速测验,看看您已经可以很好地使用这个模拟东西了。您将如何测试是否要求龟“两次准确”到达原点(您想忽略它收到的任何其他指令)?
提出答案后,请看一看我们的内容并比较笔记(自己先解决-不要作弊!):
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将立即报告错误。这基本上就是我们在上面的“使用多项期望”部分中告诉您的内容。
此示例显示**在默认情况下,即使在我们达到调用上限之后,它们仍保持活动状态,**谷歌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()
语句将匹配,并立即导致“超出上限”错误-这段代码不是很有用!
一种正确的说法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中,如果您对一种方法不感兴趣,那就什么也不要说。如果发生对此方法的调用,您将在测试输出中看到警告,但这不会失败。