news 2026/9/19 15:43:04

GoogleTest gMock(Google Mock)C++ 模拟框架实战指南:从 Mock 类编写到期望验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GoogleTest gMock(Google Mock)C++ 模拟框架实战指南:从 Mock 类编写到期望验证

GoogleTest gMock(Google Mock)C++ 模拟框架实战指南:从 Mock 类编写到期望验证

【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/gh_mirrors/googl/googletest

gMock(Google Mock)是 GoogleTest 项目中专门用于编写和使用 C++ Mock 类的子框架,它帮助开发者以声明式语法定义模拟对象、控制其行为并自动验证调用期望。本指南以 googlemock/README.md 为骨架,结合 docs/gmock_for_dummies.md 教程与仓库源码,完整讲解 Mock 对象的定义、期望(expectation)的设置、匹配器(matcher)、基数(cardinality)与动作(action)的用法,读完后你将能够独立为任意 C++ 接口编写 Mock 类并在 googletest 中完成交互式测试。

gMock 是什么

gMock 是 Google 官方推出的 C++ Mock 框架,属于 GoogleTest(Google Testing and Mocking Framework)项目的一部分,与 googletest 同源、同步发布并遵循相同的要求。它专门用于解决 C++ 测试中的一个难题:如何验证你的模块与其他模块之间的交互是否正确

gMock 的设计深受 Java/Python 生态中成熟 Mock 框架的启发,其灵感来源包括:

  • jMock(Java 动态代理式 mock 框架)
  • EasyMock(Java 经典 mock 框架)
  • Hamcrest(通用匹配器断言库)

但 gMock 不是简单照搬,而是针对 C++ 语言特性专门设计,例如通过宏与模板在编译期生成 mock 实现,并充分兼容 C++ 的虚函数、重载、const 成员函数等机制。

gMock 的核心能力

根据仓库根目录下的 googlemock/README.md,gMock 提供以下核心能力:

  • 声明式语法定义 Mock:用MOCK_METHOD宏即可声明 mock 方法,无需手写实现;
  • 支持部分(混合)Mock:可以构造真实对象与 mock 行为交叉的混合对象,mock 掉部分方法、保留真实实现;
  • 支持任意类型与重载函数:包括 const 方法、引用返回、模板参数等复杂签名;
  • 内置丰富的匹配器:用于校验函数实参,如EqGe_等;
  • 直观的行为控制语法:用WillOnceWillRepeatedly等子句描述方法被调用时的行为;
  • 自动验证期望:无需 record-and-replay 模式,调用违规会立即报错;
  • 支持任意(部分)顺序约束:可以用InSequenceAfter表达调用顺序;
  • 高度可扩展:用户可以自定义新的匹配器与动作;
  • 不使用异常:全部基于 googletest 的断言机制报告失败;
  • 易于学习和使用

这些能力在 googlemock/include/gmock/gmock.h 中得到印证——该文件是用户唯一需要包含的主头文件,它聚合导出了 actions、cardinalities、function-mocker、matchers、more-actions、more-matchers、nice-strict、spec-builders 等全部子模块,并在文件头注释中给出了ON_CALLEXPECT_CALL的完整语法骨架。

先厘清概念:Fake 与 Mock 不是一回事

在使用 gMock 之前,必须先区分两个在 TDD(测试驱动开发)社区中常被混淆的概念:

  • Fake(假对象):有可工作的实现,但通常走捷径(比如为了降低成本),因此不适合生产环境。典型例子是内存文件系统——功能完整但数据不落盘。
  • Mock(模拟对象):预先用"期望"编程的对象,这些期望构成了对它将要接收的调用的规格说明。

最核心的记忆点是:Mock 允许你检查自身与使用它的代码之间的交互。用 gMock 的流程分三步:

  1. 用简单的宏描述要 mock 的接口,宏会展开为 mock 类的实现;
  2. 创建 mock 对象,用直观的语法指定其期望与行为;
  3. 执行使用 mock 对象的代码,gMock 会在违规发生的瞬间捕获错误。

为什么需要 gMock:手写 Mock 的三大痛点

虽然 mock 对象能帮测试移除不必要的依赖、使其快速可靠,但在 C++ 中手动编写 mock 非常痛苦

  • 实现枯燥且易错:每个 mock 方法都要手写,人们宁可绕远路也不愿做;
  • 质量不可控:手工 mock 质量参差不齐,常有各种临时限制;
  • 经验不迁移:用过一个 mock 得到的经验无法复用到下一个。

相比之下,Java 和 Python 社区已有成熟框架(jMock、EasyMock 等)将 mock 创建自动化,mock 在这些社区中被证明是有效的实践。gMock 正是为 C++ 程序员补齐这一能力而生的。如果你遇到以下问题,gMock 就是你的答案:

  • 受困于次优设计,想尽早做更多原型验证,而 C++ 原型开发太慢;
  • 测试依赖太多库或昂贵资源(如数据库)而运行缓慢;
  • 测试依赖网络等不可靠资源而变得脆弱;
  • 想测试代码对失败(如文件校验和错误)的处理,但难以人为制造;
  • 需要确认模块与其他模块的交互方式正确,但难以直接观察交互,只能笨拙地在动作结束后检查副作用;
  • 想 mock 掉依赖,但依赖还没有现成的 mock 实现,又不满意手写的。

gMock 的双重价值:既是设计工具(让你早期反复试验接口设计,迭代越多设计越好),也是测试工具(切断测试的外部依赖、探测模块与协作者的交互)。

快速开始:gMock 随 googletest 一起分发

gMock 与 googletest 捆绑发布,不需要单独安装。在 CMake 工程中,可以通过根目录的 CMakeLists.txt 引入 gmock 与 gmock_main 目标;其自身的构建定义位于 googlemock/CMakeLists.txt,其中通过add_library生成gmockgmock_main两个库目标,并通过target_include_directories暴露头文件路径。编译产物由 googlemock/src/gmock-all.cc(包含全部 gmock 源码)、googlemock/src/gmock_main.cc(提供main函数)等构成。

提示:gmock_main自带main入口并自动调用InitGoogleMock(见 googlemock/include/gmock/gmock.h),它会解析 gmock 与 googletest 的命令行参数。如果使用gmock_main,测试中分配在堆上的 mock 对象会自动获得堆检查能力,从而保证析构时的期望最终验证得以执行。

实战案例:为"海龟绘图"接口编写 Mock

假设你在开发一个依赖 LOGO 风格绘图 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的析构函数必须是虚函数——所有打算被继承的类都应如此,否则通过基类指针 delete 对象时派生类析构函数不会被调用,导致内存泄漏等状态损坏。

PenUp()/PenDown()控制移动是否留下轨迹,Forward()/Turn()/GoTo()控制移动,GetX()/GetY()返回当前位置。生产代码使用真实实现,测试中则换成 mock 实现,从而轻松检查程序调用了哪些绘图原语、参数是什么、顺序如何——测试更健壮(不会因机器差异而失败)、更易读、运行快得多。

编写 Mock 类

如何定义:MOCK_METHOD 宏

按以下步骤定义MockTurtle

  1. Turtle派生MockTurtle
  2. 取一个Turtle虚函数(模板方式也能 mock 非虚方法,但复杂得多,参见 docs/gmock_cook_book.md);
  3. 在子类的public:区域写MOCK_METHOD();
  4. 把函数签名剪贴进宏:返回类型与方法名之间加一个逗号,方法名与参数列表之间再加一个逗号;
  5. mock const 方法时加第 4 个参数(const)(括号必须);
  6. 建议加override关键字——const 方法第 4 个参数写成(const, override),非 const 方法写成(override)(非强制);
  7. 重复直到所有要 mock 的虚函数完成(抽象类中所有纯虚方法必须被 mock 或 override)。

完成后效果如下:

#include <gmock/gmock.h> // 引入 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宏会自动生成定义。从源码看,该宏定义于 googlemock/include/gmock/gmock-function-mocker.h,内部通过GMOCK_PP_VARIADIC_CALL预处理变长参数并展开为GMOCK_INTERNAL_MOCK_METHOD_ARG_*模板实现,最终生成的FunctionMocker<F>负责记录调用、匹配期望与执行动作;同文件还保留了MOCK_METHOD0~MOCK_METHOD10MOCK_CONST_METHOD0~MOCK_CONST_METHOD10等旧式宏(gmock-function-mocker.h)以保证向后兼容。

放在哪里:Mock 类的组织策略

定义 mock 类时要决定放置位置:

  • 放在_test.cc:当被 mock 的接口Foo归自己团队所有时没问题;否则Foo一改,你的测试就可能崩。
  • 一般原则:不要 mock 你不拥有的类。如果必须 mock 别人拥有的类,把 mock 类定义在Foo所在的 Bazel 包(通常是同一目录或testing子目录)中,放在.h文件里,并构建为testonly=Truecc_library。这样所有人都能引用,Foo变化时只有一份MockFoo需要同步修改。
  • 另一方案:在Foo之上引入一层薄的FooAdaptor并面向新接口编程。因为FooAdaptor归你所有,能更从容地吸收Foo的变化;虽然初期工作更多,但精心设计适配器接口能让代码更易写易读。

在测试中使用 Mock

典型工作流

  1. testing命名空间导入 gMock 名称(每个文件只需一次),即可不加限定符地使用;
  2. 创建 mock 对象;
  3. 指定期望(方法被调用几次?带什么参数?应该做什么?);
  4. 执行使用 mock 的代码,可选地用 googletest 断言检查结果;若 mock 方法被多调用或参数错误,会立即报错;
  5. 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 }

如果painter没调用PenDown(),测试失败并输出如下信息:

path/to/my_test.cc:119: Failure Actual function call count doesn't match this expectation: Actually: never called; Expected: called at least once. Stack trace: ...

两个实用提示:

  • Tip 1:在 Emacs 缓冲区内运行测试时,可在失败行号上按<Enter>直接跳转到失败的期望处;
  • Tip 2:若 mock 对象永不被删除,最终验证就不会发生。因此在堆上分配 mock 时建议开启堆检查器——使用gtest_main库即可自动获得。

期望必须预先设置

重要规则:gMock 要求期望必须在 mock 函数被调用之前设置,否则行为是未定义的。不要在EXPECT_CALL()与 mock 函数调用之间交替进行,也不要在把 mock 传给某个 API 之后再设置期望。

因此EXPECT_CALL()应理解为"期望未来发生一次调用",而非"已经发生了调用"。原因在于:预先声明期望,gMock 才能在违规发生的第一时间(栈信息等上下文仍然可用的时刻)报告错误,调试会容易得多。

设置期望:掌握 EXPECT_CALL 语法

成功使用 mock 的关键是设置恰到好处的期望:太严,测试会被无关变更搞挂;太松,bug 会漏过去。gMock 提供了做到"恰到好处"所需的全部手段。

通用语法

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

这种省略参数列表的写法表示"接受任意参数",为免歧义只能用于非重载方法

语法设计得读起来像英文。例如:

using ::testing::Return; ... EXPECT_CALL(turtle, GetX()) .Times(5) .WillOnce(Return(100)) .WillOnce(Return(150)) .WillRepeatedly(Return(200));

意思是turtleGetX()将被调用 5 次:第一次返回 100,第二次返回 150,之后每次返回 200。这种风格常被称为领域特定语言(DSL)。

为什么用宏实现?两个目的:一是让期望容易被识别(无论是grep还是人眼),二是让 gMock 能在失败消息中包含期望所在的源文件位置,便于调试。

从源码看,EXPECT_CALL及配套子句定义于 googlemock/include/gmock/gmock-spec-builders.h,其完整语法还包括.With(multi-argument-matchers).InSequence(sequences).After(expectations).RetiresOnSaturation(),子句均可选,其中.InSequence()/.After()/.WillOnce()可出现任意多次。该头文件同时实现ON_CALL(mock_object, Method(...)).With(...).WillByDefault(...)用于指定 mock 方法的默认动作

Matchers:期望什么样的参数

当 mock 函数带参数时,可以指定期望的参数:

// 期望海龟前进 100 个单位。 EXPECT_CALL(turtle, Forward(100));

但经常不必如此精确——过度具体会让测试脆弱、掩盖意图。只指定必要的条件即可。对不关心的参数写_,表示"任意值都行":

using ::testing::_; ... // 期望海龟跳到 x=50 线上的某处。 EXPECT_CALL(turtle, GoTo(50, _));

_匹配器(matcher)的一个实例。匹配器像一个谓词,可检验实参是否符合预期,能用在EXPECT_CALL()中任何期望函数参数的位置。上面例子里的10050其实也是匹配器,隐式等价于Eq(100)Eq(50)——要求实参(用operator==)等于该值。常用类型的内置匹配器参见 docs/reference/matchers.md,自定义匹配器见 docs/gmock_cook_book.md:

using ::testing::Ge; ... // 期望海龟至少前进 100。 EXPECT_CALL(turtle, Forward(Ge(100)));

所有参数都不关心,可省略整个参数列表(仅限非重载方法):

// 期望海龟前进。 EXPECT_CALL(turtle, Forward); // 期望海龟跳到某处。 EXPECT_CALL(turtle, GoTo);

若方法是重载的,则需要通过指定参数个数乃至参数类型来帮助 gMock 分辨期望哪个重载。

Cardinalities:将被调用多少次

EXPECT_CALL()后的第一个可选子句是Times(),其参数称为基数(cardinality),描述调用应发生的次数。基数可以像匹配器一样"模糊",从而精确表达测试意图。

特殊情形Times(0):表示该函数完全不应以给定参数被调用,一旦被(错误地)调用,gMock 就报告 googletest 失败。

内置基数的完整列表见 docs/gmock_cheat_sheet.md;从源码看,这些工厂函数在 googlemock/include/gmock/gmock-cardinalities.h 中实现,包括AtLeast(n)AtMost(n)AnyNumber()Between(min, max)Exactly(n)。其底层是一个拷贝式、不可变的Cardinality值对象,内部通过std::shared_ptr<const CardinalityInterface>持有实现(gmock-cardinalities.h),接口提供ConservativeLowerBound()/ConservativeUpperBound()IsSatisfiedByCallCount()IsSaturatedByCallCount()等判定方法;用户也可实现CardinalityInterface定义新基数。

Times()可省略。省略时 gMock 自动推断基数,规则如下:

  • EXPECT_CALL()既无WillOnce()也无WillRepeatedly(),推断为Times(1)
  • 若有nWillOnce()WillRepeatedly()n>= 1),基数为Times(n)
  • 若有nWillOnce()一个WillRepeatedly()n>= 0),基数为Times(AtLeast(n))

小测验:若函数期望被调用两次,实际却被调用四次,会发生什么?

Actions:应该做什么

mock 对象没有真实实现,用户必须告诉它在方法被调用时做什么。

首先,默认动作: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()将被调用恰好三次(gMock 从三个WillOnce()推断出基数,因为我们没写Times()),分别返回 100、200、300。

using ::testing::Return; ... EXPECT_CALL(turtle, GetY()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillRepeatedly(Return(300));

表示turtle.GetY()将被调用至少两次(两个WillOnce()加一个WillRepeatedly()而无显式Times()),前两次分别返回 100、200,第三次起返回 300。

若显式写了Times(),gMock 不再自行推断。当指定的次数超过WillOnce()的数量时,所有WillOnce()用尽后,每次执行默认动作(除非有WillRepeatedly())。

WillOnce()里除了Return()还能做什么?可以用ReturnRef(variable)返回引用,或调用预定义函数,更多动作见 docs/gmock_cook_book.md。

重要警告EXPECT_CALL()语句对动作子句只求值一次,即使动作会被执行多次。注意副作用:

using ::testing::Return; ... int n = 100; EXPECT_CALL(turtle, GetX()) .Times(4) .WillRepeatedly(Return(n++));

并不会依次返回 100、101、102……,而是每次都返回 100,因为n++只在EXPECT_CALL()执行时求值一次。同理Return(new Foo)会在执行EXPECT_CALL()时创建一次Foo对象,之后每次返回同一个指针。若希望副作用每次发生,需要定义自定义动作(见 docs/gmock_cook_book.md)。

再一个小测验:

using ::testing::Return; ... EXPECT_CALL(turtle, GetY()) .Times(4) .WillOnce(Return(100));

turtle.GetY()被期望调用四次,但不会每次都返回 100——每次调用会消耗一个WillOnce(),之后执行默认动作。正确答案是:第一次返回 100,从第二次起返回 0int函数的默认动作)。

多个期望:倒序匹配与覆盖规则

现实中往往对多个 mock 方法(甚至多个 mock 对象)设置期望。默认情况下,mock 方法被调用时,gMock 会按期望定义的相反顺序搜索,停在第一个参数匹配的活动期望上(可理解为"新规则覆盖旧规则")。若匹配的期望已无法再接受调用,会报上界违例失败:

using ::testing::_; ... EXPECT_CALL(turtle, Forward(_)); // #1 EXPECT_CALL(turtle, Forward(10)) // #2 .Times(2);

Forward(10)连续被调用三次,第三次报错——因为最后匹配的期望(#2)已饱和。但若第三次调用的是Forward(20),则没问题——此时 #1 匹配。

为什么倒序匹配?因为这允许用户在 mock 构造函数或测试夹具的 setup 阶段设置默认期望,然后在测试体内用更具体的期望来定制。所以同一方法的两个期望,更具体的匹配器应放在后面,否则会被后面的通用规则遮蔽。

实用技巧:常用Times(AnyNumber())开一个兜底期望(非重载方法可省略参数,重载方法用_覆盖所有参数),使任何调用都在预期内。这对完全没有提及的方法("无趣"调用)不是必需的,但对已有部分期望、同时允许其他调用的方法很有用。参见 docs/gmock_cook_book.md 中 "Understanding Uninteresting vs Unexpected Calls" 一节。

顺序调用:InSequence 严格排序

默认情况下,期望不必按声明顺序满足。需要严格顺序时用InSequence

using ::testing::InSequence; ... TEST(FooTest, DrawsLineSegment) { ... { InSequence seq; EXPECT_CALL(turtle, PenDown()); EXPECT_CALL(turtle, Forward(100)); EXPECT_CALL(turtle, PenUp()); } Foo(); }

创建InSequence对象后,其作用域内的所有期望被放入序列,必须顺序发生。由于只依赖对象的构造与析构,名字无关紧要。此例验证Foo()按书写顺序调用三个函数,乱序即报错。

若只关心部分调用的相对顺序(任意偏序),gMock 也支持——细节见 docs/gmock_cook_book.md。相关的语法约束在 googlemock/test/gmock-spec-builders_test.cc 中有专门测试,例如InSequenceTest.AllExpectationInScopeAreInSequence验证作用域内全部期望入序列、ExpectCallSyntaxTest系列验证子句顺序约束(Times必须在InSequence之前等)。

期望默认是"粘性"的

小测验:如何测试海龟被要求回到原点恰好两次(忽略其他指令)?

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(总是选最后一个匹配的期望),而 #2 只允许两次调用,于是立即报错。

这个例子说明gMock 中的期望默认是"粘性"的——即使已达调用上界仍保持活动。这与许多其他 mock 框架不同,因为该规则让常见场景更容易表达和理解。

再验证一下理解:下面的代码是什么意思?

using ::testing::Return; ... for (int i = n; i > 0; i--) { EXPECT_CALL(turtle, GetX()) .WillOnce(Return(10*i)); }

如果认为GetX()会被调用 n 次并依次返回 10、20、30……就错了!期望是粘性的,第二次调用GetX()时,最后一个(最新的)EXPECT_CALL()会匹配并立即触发"上界违例"错误——这段代码毫无用处。

正确的写法是显式声明期望不粘性(饱和即退休):

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)

mock 对象往往有许多方法,并非全部有趣。如果对某方法不感兴趣,什么也不用说。该方法被调用时,测试输出会出现警告但不会失败——这称为 "naggy"(唠叨)行为。要改变这种行为,见 docs/gmock_cook_book.md 中 "The Nice, the Strict, and the Naggy" 一节。

从源码看,gMock 默认是 naggy 的,googlemock/include/gmock/gmock-nice-strict.h 实现了三个模板类:

  • NiceMock<MockFoo>:允许无趣调用(静默);
  • NaggyMock<MockFoo>:无趣调用时打印警告(当前默认行为,MockFooNaggyMock<MockFoo>表现相同);
  • StrictMock<MockFoo>:把所有无趣调用视为错误。

实现上,NiceMockImpl/NaggyMockImpl/StrictMockImpl在构造时分别调用Mock::AllowUninterestingCalls/WarnUninterestingCalls/FailUninterestingCalls注册回调,析构时注销(gmock-nice-strict.h)。三者还"继承"基类构造函数,例如NiceMock<MockFoo>(5, "a")可用于构造带参的 nice mock。已知限制:NiceMock/NaggyMock/StrictMock只对在MockFoo类中直接用MOCK_METHOD*宏定义的方法生效,基类中定义的 mock 方法可能不受影响,且不支持嵌套使用。

深入源码:EXPECT_CALL 的底层实现

理解底层实现有助于正确使用 gMock。核心组件FunctionMocker<F>UntypedFunctionMockerBase定义于 googlemock/include/gmock/gmock-spec-builders.h:

  • UntypedFunctionMockerBase是类型无关的基类,负责VerifyAndClearExpectationsLocked()(验证并清除期望)、报告无趣/意外调用消息、维护默认动作等;
  • 所有 mock 函数调用与期望操作通过静态互斥量g_gmock_mutex串行化(gmock-spec-builders.h),保证多线程环境下 mock 对象状态的一致性——这正是"期望匹配采用倒序搜索"能够可靠实现的并发前提;
  • TypedExpectation<F>是类型化的期望实现,持有 matcher、cardinality、action 序列与 retirement 状态。

因此,EXPECT_CALL(turtle, GetX()).Times(5).WillOnce(...)实际上是在构造一个TypedExpectation,将其注册进FunctionMocker的期望列表中;每次真实调用发生时,mocker 会加锁、倒序遍历期望、用 matcher 匹配实参、按 cardinality 判断是否可接受,再执行对应 action——违规(超调、参数不匹配、顺序错误)会立即转成 googletest 断言失败。

延伸阅读

gMock 的完整文档体系位于仓库的 docs 目录,可按需深入:

  • docs/gmock_for_dummies.md:gMock 入门教程(本文主要依据);
  • docs/gmock_cook_book.md:gMock 高级用法手册(自定义动作与匹配器、部分顺序、Nice/Strict/Naggy 等);
  • docs/gmock_cheat_sheet.md:语法速查表(含基数列表);
  • docs/gmock_faq.md:gMock 常见问题;
  • docs/reference/matchers.md:内置匹配器完整参考;
  • docs/reference/actions.md 与 docs/reference/mocking.md:动作与 mock 语法参考。

同时可阅读 googlemock/test/gmock-spec-builders_test.cc 等测试文件,观察EXPECT_CALL各子句在真实用例中的组合方式;全部测试由 googlemock/test/BUILD.bazel 组织,可用 Bazel 或 CMake 直接运行验证本文示例的行为。

【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/gh_mirrors/googl/googletest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 15:35:29

Claude Code 实战:TaoToken 跑通 TypeScript 仓库依赖清理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 15:31:49

STM32 USB虚拟串口避坑指南:从CubeMX配置到量产稳定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 15:31:06

基于STM32的仿生六足机器人舵机控制系统设计与实现

简介&#xff1a;这份基于STM32的仿生六足机器人控制系统设计文档&#xff0c;面向嵌入式系统学习者、机器人竞赛参赛者及电子相关专业学生&#xff0c;旨在帮助读者掌握足式机器人控制系统的完整设计思路。文档以STM32F103VCT6为主控芯片&#xff0c;系统讲解了六足机器人行进…

作者头像 李华
网站建设 2026/9/19 15:29:26

管理信息系统复习重点:从信息决策到开发方法的系统梳理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 15:29:11

去耦电容放错位置为何导致EMC辐射不降反增

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华