ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

gMock 入门指南:在 GoogleTest(GoogleTest 仓库)中编写 Mock 类与期望(Expectation)的完整实践

2026/9/6 15:29:44 拓冰建站 浏览量
gMock 入门指南:在 GoogleTest(GoogleTest 仓库)中编写 Mock 类与期望(Expectation)的完整实践 gMock 入门指南在 GoogleTestGoogleTest 仓库中编写 Mock 类与期望Expectation的完整实践【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest本文基于 GoogleTest 仓库的官方入门文档 gmock_for_dummies.md 整理并深化系统讲解 gMock 的核心概念mock 与 fake 的区别、mock 类定义规则、MOCK_METHOD宏的编写规范以及EXPECT_CALL期望体系matchers、cardinalities、actions、顺序约束、sticky 语义的使用方法。读完后你将能够独立完成定义 mock 类 → 设置期望 → 运行被测代码 → 理解失败输出的完整 C mock 测试流程并从源码层面理解这些语法背后的实现机制。什么是 gMockMock 对象与 Fake 对象的区别编写原型或测试时完全依赖真实对象往往既不可行也不明智。Mock 对象实现了与真实对象相同的接口因此可以替代它使用但允许你在运行时指定它应如何被使用哪些方法会被调用以什么顺序调用多少次参数是什么应该返回什么等。在 TDD测试驱动开发社区中fake 对象和mock 对象是极易混淆但含义不同的两个概念Fake假对象拥有可工作的实现但通常走了一些捷径例如让操作开销更低因此不适合用于生产环境。一个典型例子是内存文件系统。Mock模拟对象预先用expectations期望编程的对象这些期望构成了它所应接受的调用的规格说明。如果上述定义显得抽象请记住最重要的一点mock 让你能够检查它自身与使用方代码之间的交互interaction。当你真正开始使用 mock 后fake 与 mock 的区别会变得更加清晰。gMock是一个用于创建和使用 mock 类的库为了让它听起来更酷我们有时也称其为框架。它对 C 所做的事情大致相当于 jMock/EasyMock 对 Java 所做的事情姑且这么说。使用 gMock 的整体流程分三步先用几个简单的宏描述你希望 mock 的接口宏会展开为 mock 类的实现然后创建 mock 对象用一套直观的语法指定其期望与行为接着运行使用这些 mock 对象的代码。gMock 会在任何期望被违反的瞬间捕获该错误。为什么需要 gMock手写 Mock 在 C 中有多难Mock 对象能帮助测试去除不必要的依赖使测试更快、更可靠。但在 C 中手动使用 mock 非常困难总得有人去实现这些 mock。这项工作通常枯燥且易出错——难怪人们会拼命回避它。手写 mock 的质量相当……难以预测。你既能看到打磨得很精致的也能看到匆忙拼凑、带有一堆临时限制的。从使用某一个 mock 中学到的经验无法迁移到下一个 mock。相比之下Java 和 Python 程序员拥有优秀的 mock 框架jMock、EasyMock 等它们自动完成 mock 的创建。因此mocking 在这些社区中已被证明是一种有效的、被广泛采用的技术。拥有合适的工具本身就能产生巨大的差别。gMock 正是为帮助 C 程序员而生的。它受 jMock 和 EasyMock 启发但针对 C 的语言特性专门设计。如果你正被以下问题困扰gMock 就是你的朋友你被一个次优设计困住早该在太晚之前多做些原型验证但 C 中的原型开发绝非快速的。测试很慢因为它们依赖过多库或昂贵的资源例如数据库。测试很脆弱因为某些所用资源不可靠例如网络。你想测试代码如何处理某种故障例如文件校验和错误但故障难以人为制造。你需要确认本模块与其他模块以正确的方式交互但直接观察交互很难只能别扭地去观察操作结束后的副作用。你想mock 掉某些依赖但它们还没有 mock 实现而且坦白说你对其中一些手写 mock 并不满意。官方建议你把 gMock 当作两类工具来用一个设计工具——它让你能尽早、频繁地实验接口设计。迭代次数越多设计就越好一个测试工具——裁掉测试的外向依赖探测你的模块与其协作方之间的交互。gMock 随 googletest 一起捆绑发布无需单独安装构建当前仓库时googlemock/目录与googletest/目录由顶层 CMakeLists.txt 统一管理。一个案例Mock 乌龟Mock Turtle假设你在开发一个依赖 LOGO 风格绘图 API 的图形程序如何测试它做对了事情一种做法是运行它并和黄金屏幕截图比对但请承认这类测试运行昂贵且脆弱刚升级到一块抗锯齿更好的显卡所有黄金图片就得全部更新。如果所有测试都是这样那将痛不欲生。幸运的是你已了解依赖注入Dependency Injection知道正确的做法是不要让应用程序直接调用系统 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的析构函数必须是 virtual 的——事实上所有打算被继承的类都应如此。否则通过基类指针 delete 对象时派生类的析构函数不会被调用你将得到内存泄漏等程序状态损坏。你可以用PenUp()和PenDown()控制乌龟的移动是否留下痕迹用Forward()、Turn()和GoTo()控制其移动最后用GetX()和GetY()获取当前坐标。程序中平时使用接口的真实实现测试中则可以使用 mock 实现替代。这让你可以轻松检查程序调用了哪些绘图原语、带了什么参数、以什么顺序调用。这样写出的测试要健壮得多不会因为新机器抗锯齿方式不同而失败、更易读和易维护测试意图写在代码里而不是某些二进制图片中而且运行快得多、快得多。编写 Mock 类如果运气好你要用的 mock 类可能已被别人实现了。但如果你需要自己写 mock 类别担心——gMock 把这项任务变成了一场有趣的游戏好吧差不多吧。MOCK_METHOD如何定义 Mock 类以Turtle接口为例需要遵循的步骤很简单从Turtle派生一个MockTurtle类。取出Turtle的一个virtual函数虽然可以用模板 mock 非虚方法但过程要复杂得多。在子类的public:区段中写下MOCK_METHOD();现在到了有趣的部分把函数签名剪切粘贴进宏里并加两个逗号——一个在返回类型和方法名之间另一个在方法名和参数列表之间。如果 mock 的是 const 方法添加包含(const)的第 4 个参数括号是必需的。由于你在重写虚方法建议加上override关键字。const 方法的第 4 个参数写作(const, override)非 const 方法写作(override)。这不是强制的。重复以上过程直到所有要 mock 的虚函数都写完。显然抽象类中所有纯虚方法都必须要么被 mock要么被重写。完成之后代码应类似#include gmock/gmock.h // Brings in gMock. class MockTurtle : public Turtle { public: ... MOCK_METHOD(void, PenUp, (), (override)); MOCK_METHOD(void, PenDown, (), (override)); MOCK_METHOD(void, Forward, (int distance), (override)); MOCK_METHOD(void, Turn, (int degrees), (override)); MOCK_METHOD(void, GoTo, (int x, int y), (override)); MOCK_METHOD(int, GetX, (), (const, override)); MOCK_METHOD(int, GetY, (), (const, override)); };你不需要在别处定义这些 mock 方法——MOCK_METHOD宏会为你生成定义。就是这么简单源码视角MOCK_METHOD 宏到底做了什么从源码结构看MOCK_METHOD是一个变参宏其定义位于 gmock-function-mocker.h。宏体本身非常薄#define MOCK_METHOD(...) \ GMOCK_INTERNAL_WARNING_PUSH() \ GMOCK_INTERNAL_WARNING_CLANG(ignored, -Wunused-member-function) \ GMOCK_PP_VARIADIC_CALL(GMOCK_INTERNAL_MOCK_METHOD_ARG_, __VA_ARGS__) \ GMOCK_INTERNAL_WARNING_POP()它按参数个数分发到GMOCK_INTERNAL_MOCK_METHOD_ARG_3或_ARG_43 参形式会自动补上默认 spec()4 参形式则调用GMOCK_INTERNAL_ASSERT_VALID_SPEC校验第 4 个参数。该校验的合法取值在 gmock-function-mocker.h 的ValidateSpec中枚举const、override、final、noexcept可带参数、ref()、ref()、Calltype(...)。这与入门文档中第 4 个参数用于标注 const/override的说明完全一致同时说明当前仓库的 spec 还支持final、noexcept等更丰富的修饰。展开后宏会生成一个完整的虚函数定义含override/const等修饰并在类内生成一组内部方法gmock_##方法名即gmock_PenUp、gmock_Forward……。正是这些gmock_方法构成了EXPECT_CALL的落点——这一联系在下一节的宏展开中可以看到。把 Mock 类放在哪里定义 mock 类时需要决定其定义放在何处。有人把它放进某个_test.cc。当被 mock 的接口比如Foo由同一个人或团队负责时这样做没问题。否则当Foo的负责人修改接口时你的测试就可能被打破总不能指望Foo的维护者去修复每个用到Foo的测试吧。一般原则是不要 mock 你不拥有的类。如果确实必须 mock 别人拥有的类请把 mock 类定义在Foo所在的 Bazel 包中通常在同一目录或其testing子目录放进一个.h文件和带testonlyTrue的cc_library中。这样所有人都能在测试中引用它们一旦Foo发生变化只有一份MockFoo需要修改也只有依赖了被改方法的测试需要修复。另一种做法在Foo之上引入一个薄薄的FooAdaptor层面向这个新接口编程。由于FooAdaptor归你所有你可以更从容地吸收Foo的变化。虽然初期工作量更大但精心选择的 adaptor 接口往往能让代码更易写、更易读长期是净收益因为你可以让FooAdaptor比Foo本身更贴合你的特定领域。在测试中使用 Mock有了 mock 类之后使用它轻而易举。典型的工作流是从testing命名空间导入 gMock 的名字使其可以不加限定地使用每个文件只需做一次。别忘了使用命名空间是个好习惯。创建若干 mock 对象。为它们设置期望某方法会被调用多少次参数是什么它应该做什么等等。运行使用这些 mock 的代码可选地用 googletest 断言检查结果。如果某个 mock 方法被调用的次数超出期望、或参数不对你会立刻收到错误。当 mock 被析构时gMock 会自动检查作用于它的所有期望是否都已满足。示例如下#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 }如你所料这个测试检查PenDown()至少被调用一次。如果painter对象没调用该方法测试会以类似如下的消息失败path/to/my_test.cc:119: Failure Actual function call count doesnt match this expectation: Actually: never called; Expected: called at least once. Stack trace: ...Tip 1如果你在 Emacs 缓冲区中运行测试可以在行号上按Enter直接跳转到失败的期望处。Tip 2如果你的 mock 对象从未被删除最终的期望校验就不会发生。因此在堆上分配 mock 时建议在测试中打开堆检查器。使用gtest_main库时你自动获得该能力。源码视角析构时校验与泄漏检测的机制析构时自动校验期望并非空口承诺。从源码结构看gmock-spec-builders.cc 中维护着一个全局的MockObjectRegistryg_mock_object_registry每个 mock 对象首次被Mock::AllowLeak()、ON_CALL()或EXPECT_CALL()使用时被登记记录首次使用的文件、行号、所在测试对象析构时从注册表移除。注册表析构函数在程序退出时运行若有 mock 对象仍然存活且未被标记为可泄漏就会打印ERROR: this mock object ... should be deleted but never is并明确解释——mock 对象的期望在其析构时被校验泄漏 mock 意味着其期望得不到校验这通常是测试 bug还提示可用testing::Mock::AllowLeak(mock_object)抑制。对应的行为有测试用例佐证如 gmock_leak_test_.cc 中的CatchesMultipleLeakedMockObjects。这就是为什么 Tip 2 强调要确保 mock 被删除。期望必须先于调用设置Expectation Ordering重要说明gMock 要求期望必须在 mock 函数被调用之前设置否则行为是未定义的。不要交替执行EXPECT_CALL()和对 mock 函数的调用也不要在把 mock 传给某个 API 之后再对它设置任何期望。也就是说EXPECT_CALL()应当被理解为期望某次调用将来会发生而不是某次调用已经发生。gMock 为何这样设计因为事先声明期望使 gMock 能在违规发生的第一时间此时栈回溯等上下文仍可获得上报它从而大大方便调试。承认这个测试有些刻意、作用有限——不用 gMock 也能达到同样效果。但正如即将看到的gMock 允许你用 mock 做多得多的事。设置期望Setting Expectations成功使用 mock 对象的关键是设置恰当的期望。期望设置得过于严格测试会因无关变更而失败设置得过于宽松bug 就会溜进来。你希望做到刚刚好使测试恰好能捕获你意图捕获的那类 bug。gMock 提供了做到刚刚好所需的全部手段。通用语法gMock 使用EXPECT_CALL()宏为 mock 方法设置期望通用语法为EXPECT_CALL(mock_object, method(matchers)) .Times(cardinality) .WillOnce(action) .WillRepeatedly(action);宏有两个参数先是 mock 对象然后是方法及其参数。注意二者用逗号,而不是句点.分隔。为什么用逗号出于技术上的必要性。如果方法没有重载宏也可以不带 matcher 列表地调用EXPECT_CALL(mock_object, non-overloaded-method) .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()方法将被调用 5 次第一次返回 100第二次返回 150此后每次都返回 200。有些人把这种语法风格称为领域特定语言DSL。Note为什么用宏来实现它有两个目的第一使期望易于识别无论用grep还是人眼第二让 gMock 能把失败的期望所在的源码位置包含进消息便于调试。源码视角EXPECT_CALL 宏的展开失败位置能出现在消息里这一点在源码中可以直接验证。gmock-spec-builders.h 中#define GMOCK_ON_CALL_IMPL_(mock_expr, Setter, call) \ ((mock_expr).gmock_##call)(::testing::internal::WithoutMatchers::Get(), \ nullptr) \ .Setter(__FILE__, __LINE__, #mock_expr, #call) #define EXPECT_CALL(obj, call) \ GMOCK_ON_CALL_IMPL_(obj, InternalExpectedAt, call)也就是说EXPECT_CALL(turtle, GetX())最终展开为对turtle.gmock_GetX(...)的调用这正是MOCK_METHOD生成的gmock_方法的用途并链式调用InternalExpectedAt(__FILE__, __LINE__, ...)——文件与行号就此被固化进期望对象失败时打印的path/to/my_test.cc:119: Failure正源于此。头文件中的注释还解释了逗号的技术原因WithoutMatchers::Get()参数用于消除gmock_Method两组重载之间的二义性同时防止调用者意外直接触发第二组重载而对重载方法若不给 matcher 列表编译器会报call to member function gmock_Method is ambiguous——这就是无参形式仅适用于非重载方法的底层原因。Matchers期望什么参数当 mock 函数带参数时可以指定期望的参数例如// Expects the turtle to move forward by 100 units. EXPECT_CALL(turtle, Forward(100));很多时候你不想过于具体。还记得前面谈到的测试过于死板吗过度规格化会产生脆弱的测试并模糊测试意图。因此官方鼓励你只指定必要的部分——不多也不少。如果不在乎某个参数的取值就写_表示任何值都行using ::testing::_; ... // Expects that the turtle jumps to somewhere on the x50 line. EXPECT_CALL(turtle, GoTo(50, _));_是所谓matcher匹配器的一个实例。matcher 类似谓词可检验一个参数是否符合预期。凡是在EXPECT_CALL()中期待函数参数的位置都可以使用 matcher_是表达任意值的便捷方式。上面的例子里100和50也是 matcher隐式地它们等同于Eq(100)和Eq(50)即指定参数必须用operator等于给定的值。针对常见类型有许多内置 matcher也可以自定义 matcher。例如using ::testing::Ge; ... // Expects the turtle moves forward by at least 100. EXPECT_CALL(turtle, Forward(Ge(100)));如果你完全不在乎参数可以省略整个参数列表而不是给每个参数都写_// Expects the turtle to move forward. EXPECT_CALL(turtle, Forward); // Expects the turtle to jump somewhere. EXPECT_CALL(turtle, GoTo);这对所有非重载方法有效如果方法被重载你需要通过指定参数个数、乃至参数类型来帮助 gMock 分辨你期望的是哪个重载。Cardinalities会被调用多少次EXPECT_CALL()之后可以指定的第一个子句是Times()。它的参数称为cardinality基数因为它指明调用应发生多少次。它让你不必把同一期望写很多遍就能重复一个期望。更重要的是基数可以像 matcher 一样模糊从而让你精确表达测试意图。一个有趣的特例是Times(0)它表示该函数用给定参数一次都不应被调用gMock 会在函数被错误地调用时报告 googletest 失败。前面见过模糊基数AtLeast(n)的例子。可用的内置基数清单见速查表。Times()子句可以省略。如果省略gMock 会为你推断基数规则易于记忆若EXPECT_CALL()中既没有WillOnce()也没有WillRepeatedly()推断基数为Times(1)。若有n个WillOnce()但没有WillRepeatedly()n 1基数为Times(n)。若有n个WillOnce()和一个WillRepeatedly()n 0基数为Times(AtLeast(n))。小测验若某函数期望被调用两次实际却被调用了四次会发生什么Actions它应该做什么记住 mock 对象并没有真正可工作的实现——作为使用者你必须告诉它方法被调用时该做什么。在 gMock 中这很容易。首先如果 mock 函数的返回类型是内置类型或指针该函数有一个默认动作default actionvoid函数直接返回bool函数返回false其他函数返回 0。此外在 C11 及以后返回类型默认可构造有默认构造函数的 mock 函数其默认动作为返回默认构造值。如果你什么都不说就会采用这种行为。其次如果 mock 函数没有默认动作或默认动作不合意你可以用一系列WillOnce()子句外加一个可选的WillRepeatedly()指定每次期望匹配时要执行的动作。例如using ::testing::Return; ... EXPECT_CALL(turtle, GetX()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillOnce(Return(300));表示turtle.GetX()将被调用恰好三次由于未显式写Times()gMock 从你写了几个WillOnce()推断出次数并分别返回 100、200、300。using ::testing::Return; ... EXPECT_CALL(turtle, GetY()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillRepeatedly(Return(300));表示turtle.GetY()将被调用至少两次写了两个WillOnce()和一个WillRepeatedly()、且无显式Times()gMock 由此得知前两次分别返回 100 和 200第三次起返回 300。当然如果你显式写了Times()gMock 就不会自行推断基数。那么指定的次数比WillOnce()的个数多怎么办所有WillOnce()用尽之后gMock 每次执行该函数的默认动作除非你写了WillRepeatedly()。WillOnce()里除了Return()还能做什么可以用ReturnRef(variable)返回引用也可以调用预定义函数以及更多动作。重要说明EXPECT_CALL()语句只对 action 子句求值一次尽管该动作可能被执行多次。因此你必须小心副作用。下面的代码可能不符合你的预期using ::testing::Return; ... int n 100; EXPECT_CALL(turtle, GetX()) .Times(4) .WillRepeatedly(Return(n));它不会依次返回 100、101、102……而是总是返回 100因为n只求值一次。同理Return(new Foo)会在EXPECT_CALL()执行时创建Foo对象之后每次返回同一个指针。若希望副作用每次调用都发生需要定义自定义 action详见烹饪手册。再来一题下面这段代码意味着什么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到目前为止示例都只有单个期望。更现实的场景是你对多个 mock 方法可能来自多个 mock 对象设置期望。默认情况下当某个 mock 方法被调用时gMock 按期望定义的逆序查找找到第一个与参数匹配且仍处于活跃状态的期望即停可以理解为新规则覆盖旧规则。如果匹配到的期望已不能再接受调用你得到的就是上界越界upper-bound-violated失败。示例using ::testing::_; ... EXPECT_CALL(turtle, Forward(_)); // #1 EXPECT_CALL(turtle, Forward(10)) // #2 .Times(2);如果Forward(10)连续被调用三次第三次就是错误因为最后匹配的期望#2已饱和saturated。但若把第三次Forward(10)换成Forward(20)那就没问题了——此时 #1 成为匹配的期望。Note为什么 gMock 按逆序查找匹配因为这样用户就可以在 mock 对象构造函数或测试夹具的 setup 阶段设置默认期望然后在测试体中用更具体的期望来定制 mock。因此如果你对同一方法有两个期望应把 matcher 更具体的那个写在后面否则更具体的规则会被其后更一般的规则遮蔽。Tip很常见的一种起手式为某方法先建一个兜底期望并配Times(AnyNumber())非重载方法可省略参数重载方法则对所有参数写_这样对该方法的一切调用都在预期之内。对完全没被提到的方法所谓不感兴趣的方法没必要这样做但对于已有若干期望、同时又允许其他调用发生的方法这一招很有用。参见理解 Uninteresting 与 Unexpected 调用。源码视角不感兴趣调用的默认反应是警告未设置期望的调用只告警不失败naggy 行为在源码中也有对应gmock-spec-builders.cc 中的intToCallReaction把未识别的 mock 行为取值兜底为kWarn且该文件维护了一个UninterestingCallReactionMapL543-L549把每个 mock 对象映射到遇到不感兴趣的调用时 gMock 应如何反应。换言之默认 naggy是注册表层面的默认策略而 gmock-nice-strict.h 提供的NiceMock/StrictMock包装正是用来改写这一反应的入口。有序调用 vs 无序调用Ordered vs Unordered Calls默认情况下即使较早的期望尚未满足一个期望仍可匹配一次调用——换言之调用不必按期望声明的顺序发生。但有时你希望所有期望的调用严格按顺序发生。在 gMock 中表达这一点很容易using ::testing::InSequence; ... TEST(FooTest, DrawsLineSegment) { ... { InSequence seq; EXPECT_CALL(turtle, PenDown()); EXPECT_CALL(turtle, Forward(100)); EXPECT_CALL(turtle, PenUp()); } Foo(); }创建InSequence类型的对象后其作用域内的所有期望被放入一个序列sequence必须顺序发生。由于实际工作只依赖该对象的构造函数和析构函数它的名字其实无关紧要。在这个例子中你测试的是Foo()按书写顺序调用这三个函数。若调用顺序错误即为错误。如果你只关心部分调用之间的相对顺序而非全部呢能否指定任意偏序答案是……能细节见这里。所有期望默认都是粘性的Sticky来做个小测验检验一下你对 mock 的掌握程度如何测试乌龟被要求恰好两次回到原点其他指令一律忽略先自己想想再对比我们的答案using ::testing::_; using ::testing::AnyNumber; ... EXPECT_CALL(turtle, GoTo(_, _)) // #1 .Times(AnyNumber()); EXPECT_CALL(turtle, GoTo(0, 0)) // #2 .Times(2);假设turtle.GoTo(0, 0)被调用了三次。第三次时gMock 发现参数匹配期望 #2记住总是选最后匹配的期望。而我们又声明这样的调用只应出现两次于是 gMock 立即报错。这本质上就是前面使用多个期望一节讲过的规则。这个例子说明gMock 中的期望默认是粘性sticky的——即使已达到调用上界期望仍然保持活跃。这是一条必须牢记的重要规则它影响规格的含义且与许多其他 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……的一种正确方式是显式声明这些期望不粘——即它们一旦饱和就退役retireusing ::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一个 mock 对象可能有很多方法并非都那么值得关注。例如某些测试中我们可能完全不在乎GetX()和GetY()被调用多少次。在 gMock 中如果你不关心某个方法就干脆什么都不说。若该方法的调用真的发生你会在测试输出中看到一条警告但这不算失败。这种行为叫做naggy唠叨型如需修改例如改为完全安静或严格失败参见The Nice, the Strict, and the Naggy。小结这篇入门指南给出了一条清晰的主线定义用MOCK_METHOD宏从接口派生 mock 类注意第 4 个 spec 参数(const)/(override)等的括号规则mock 类的存放位置应遵循只 mock 你拥有的接口原则使用按创建 mock → 设置期望 → 运行代码 → 依赖析构时自动校验的五步工作流编写测试期望必须先于调用设置调用违规会即时失败精准表达用 matchers 控制哪些参数、用 cardinalities 控制多少次、用 actions 控制做什么牢记省略Times()时的三条推断规则、期望的粘性语义必要时用RetiresOnSaturation()以及逆序匹配规则更具体的期望写在后面原理可溯EXPECT_CALL展开为gmock_方法调用并携带__FILE__/__LINE__gmock-spec-builders.hMOCK_METHOD按参数个数分发并校验 specgmock-function-mocker.hmock 对象的登记、泄漏检测与退出时报错由 MockObjectRegistry 实现。掌握以上内容后可进一步阅读 gmock_cook_book.md进阶技巧自定义 matcher、自定义 action、有序调用、Nice/Strict/Naggy 行为等、gmock_cheat_sheet.md速查表与 gmock_faq.md常见问题以及 reference/matchers.md 中的完整 matcher 参考。【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考