GMock 简易教程

gMock 是 Google 开发的一个 C++ 模拟框架,它让你可以用声明式的方式定义模拟对象,并在测试中精确验证对象之间的交互。本文带你从零开始,系统了解 gMock 的核心概念和日常用法。

什么是 gMock?

在写原型或单元测试时,直接依赖真实对象经常不可行或不划算:数据库太慢、网络不可靠、某些错误场景难以触发。模拟对象(mock object) 实现了和真实对象一样的接口,但允许你在运行时指定:

  • 哪些方法会被调用
  • 以什么顺序、多少次
  • 传入什么参数
  • 各次调用返回什么值

这样你就能在完全可控的环境下测试自己的代码。

伪对象(fake)与模拟对象(mock)的区别

  • 伪对象 有可工作的实现,但往往为测试做了简化(比如内存文件系统),不适合生产环境。
  • 模拟对象 并不真正实现业务逻辑,而是预先编程了“期望”——包括应该收到怎样的调用。它被用来 检验代码与依赖之间的交互
    gMock 是用来创建和使用这种模拟对象的库。

为什么要用 gMock?

手动在 C++ 里写模拟类很痛苦:重复、容易出错,而且每次都要从头开始,知识无法复用。gMock 提供了自动生成模拟类的宏,让你可以专注于表达“应该发生什么”,而不是怎么写桩代码。
如果你有以下困扰,gMock 很适合你:

  • 想快速做接口原型,但在 C++ 里写原型太慢。
  • 测试依赖太重(数据库、网络等),导致测试慢且不稳定。
  • 很难构造某些失败场景来验证错误处理。
  • 想验证模块之间的 调用方式和交互过程,而不是只看最终副作用。

你可以把 gMock 当作:

  • 设计工具:快速试验接口设计,通过频繁迭代得到更好的架构。
  • 测试工具:隔离外部依赖,并精确探查模块间的交互。

    快速开始:模拟一只海龟

    假设你正在开发一个绘图程序,它通过类似 LOGO 的海龟 API 来画图:

    class Turtle {
     public:
      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;
    };

    为了测试程序是否正确地调用了这些绘图原语(而不是去比对屏幕截图),你可以用 gMock 生成一个模拟的海龟,在测试中使用。

编写模拟类

基本步骤

以 Turtle 为例,创建一个模拟类 MockTurtle

  1. 公开继承 Turtle
  2. 在 public: 区域内,对每一个虚函数使用 MOCK_METHOD 宏。
  3. 宏的参数依次为:返回类型、方法名、参数列表、修饰符(如 constoverride)。
    修饰符参数需要外加括号。

对于 Turtle,写法如下:

#include <gmock/gmock.h>

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

宏会自动生成方法实现,不需要再写额外的定义。

放在哪里

  • 如果被模拟的接口(比如 Foo)归你自己的团队所有,可以直接把 MockFoo 写在 *_test.cc 测试文件里。
  • 如果要模拟 不属于你 的类(例如外部库的接口),最好在一个独立的 .h 和 cc_library(标记 testonly=True)中定义模拟类,放在与该接口同一包的 testing 子目录下。这样当接口变化时,只需修改一份 MockFoo
  • 另一种做法:为外部依赖引入一个自己拥有的薄适配层,然后针对适配器接口写模拟。这会增加一些前期工作,但长期看可以让你的代码更贴近领域需求,降低耦合。

在测试中使用模拟类

基本工作流:

  1. 导入 gMock 名称(例如 using ::testing::AtLeast;)。
  2. 创建模拟对象。
  3. 在模拟对象上设置期望(调用次数、参数、行为等)。
  4. 运行被测代码,使用 googletest 断言检查结果。如果调用不符合期望,gMock 会立刻失败并给出详细信息。
  5. 测试结束时,gMock 自动检查是否所有期望都已满足。

示例:验证画家在画圆时至少抬起笔一次。

#include "path/to/mock-turtle.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>

using ::testing::AtLeast;

TEST(PainterTest, CanDrawSomething) {
  MockTurtle turtle;
  EXPECT_CALL(turtle, PenDown())
      .Times(AtLeast(1));

  Painter painter(&turtle);
  EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}
注意:期望必须在调用模拟方法之前设置。不要在调用之后再补设期望,否则行为未定义。预先设置期望能让 gMock 在违规的第一时间报告失败,并保留完整的调用栈,方便调试。

设置期望

成功使用模拟的关键在于“恰到好处”地设置期望——既不过宽而漏掉错误,也不过严而让无关改动导致测试失败。

通用语法

EXPECT_CALL(mock_object, method(matchers))
    .Times(cardinality)
    .WillOnce(action)
    .WillRepeatedly(action);

EXPECT_CALL(mock对象, 方法名(参数匹配器))
    .Times(调用次数期望)
    .WillOnce(第一次返回什么)
    .WillRepeatedly(默认返回什么);

两个参数分别是模拟对象和方法调用。对象与方法之间用逗号分隔。如果方法没有重载,也可以省略匹配器,直接写成:

EXPECT_CALL(mock_object, non-overloaded-method)
    .Times(cardinality)
    ...

这种写法表示“不管参数是什么,只要调用就算”。
后面的 .Times().WillOnce() 等子句都是可选的。这条语法刻意接近英文表达,例如:

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

意思是:GetX() 将被调用 5 次,第 1 次返回 100,第 2 次返回 150,剩下 3 次都返回 200。

  • WillRepeatedly 是在当前 EXPECT_CALL 的 WillOnce 序列用尽后,为后续所有剩余匹配调用提供统一返回值的“局部兜底动作”,即优先级仅次于 WillOnce 的局部默认值。

参数匹配器(Matchers)

你可以精确指定方法调用时应传入什么参数:

// 要求 Forward 参数正好是 100
EXPECT_CALL(turtle, Forward(100));

不想对值做限制时,用通配符 _ (“任意值”):

using ::testing::_;
// 要求 GoTo 的 x 坐标为 50,y 无所谓
EXPECT_CALL(turtle, GoTo(50, _));

实际上 10050 也是匹配器,相当于 Eq(100)Eq(50)。你还可以使用更多内置匹配器,例如:

using ::testing::Ge;
// 前进距离至少 100
EXPECT_CALL(turtle, Forward(Ge(100)));

如果所有参数都不关心,可以直接省略参数列表(仅适用于非重载方法):

EXPECT_CALL(turtle, Forward);   // 任意参数均可

调用次数(Cardinalities)

Times() 子句指定期望的调用次数。参数可以是精确值,也可以是“模糊”基数,比如 AtLeast(n)。常见的内置基数有:

  • Exactly(n) :调用次数 == n 或直接写 n
  • AtLeast(n):调用次数 >= n。
  • AtMost(n):调用次数 <= n。
  • Between(m, n):m <= 调用次数 <= n。
  • 补充:未指定时,默认是 Exactly(1),但若有 WillRepeatedly 则隐含 AtLeast(1)(因此即使调用多次也不会失败)。我应该添加这个重要的说明。

如果省略 Times(),gMock 会根据 WillOnce 和 WillRepeatedly 的个数推断:

  • 既没有 WillOnce 也没有 WillRepeatedly:推断为 Times(1)
  • 有 n 个 WillOnce 且没有 WillRepeatedly:推断为 Times(n)
  • 有 n 个 WillOnce 和一个 WillRepeatedly:推断为 Times(AtLeast(n))
    若调用次数超出上限,会立即触发失败。

动作(Actions)

模拟方法本身没有真实逻辑,需要你通过 动作 指定其行为。

  • 对于返回内建类型或指针的函数,gMock 有默认动作:void 函数直接返回,bool 返回 false,其他返回 0
  • 如果返回类型可默认构造(C++11 起),也会返回默认值。
  • 显式指定动作时,使用 WillOnce(一次性动作)和 WillRepeatedly(后续重复动作):
EXPECT_CALL(turtle, GetX())
    .WillOnce(Return(100))
    .WillOnce(Return(200))
    .WillOnce(Return(300));

Return() 是最常用的动作,还可以用 ReturnRef(variable) 返回引用,或者调用函数、抛异常等。

注意副作用:动作子句在 EXPECT_CALL 语句执行时只会被评估 一次。因此 Return(n++) 不会每次调用都递增,而是每次返回相同的初始值。如果需要每次产生不同结果,需要自定义动作。

小测验:以下代码的含义是什么?

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

答案:GetY() 预期被调用 4 次。第 1 次返回 100;之后 3 次执行默认动作(int 默认返回 0)。因为每个 WillOnce 只消耗一次,消耗完后会回退到默认动作,除非有 WillRepeatedly

多个期望的匹配规则

当一个模拟方法有多个期望时,gMock 按 定义的相反顺序 进行匹配(即后定义的期望优先)。如果最后匹配的期望已经饱和(达到调用上限),则报错。
例如:

EXPECT_CALL(turtle, Forward(_));     // #1 捕获所有
EXPECT_CALL(turtle, Forward(10))     // #2 只匹配参数 10
    .Times(2);
  • 调用 Forward(10) 会优先命中 #2。
  • 当 #2 的两次配额用完,第三次 Forward(10) 会因饱和而失败。
  • 此时如果调用 Forward(20),则 #1 可以接住,不会报错。

    这种“后定义优先”的规则允许你在测试夹具或构造函数中设定默认期望,然后在具体测试用例里用更特殊的期望覆盖。

有序调用与无序调用

默认情况下,gMock 不要求调用顺序与期望声明顺序一致。如果你需要严格顺序,可以使用 InSequence 对象:

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

在 InSequence 作用域内的所有期望,调用必须严格按声明顺序发生,否则测试失败。gMock 还支持指定部分偏序关系,详情可查阅官方文档。

粘性期望与 RetiresOnSaturation

默认情况下,gMock 的期望是 粘性(sticky) 的:即使已达到调用上限,该期望仍然保持活动,当后续调用参数再次匹配时会立即报告“上限违反”。这与其他一些框架不同,但能让常见场景的表达更简单。
如果你希望期望饱和后自动失效,不再参与匹配,可以加上 RetiresOnSaturation()

EXPECT_CALL(turtle, GetX())
    .WillOnce(Return(100))
    .RetiresOnSaturation();

此外,处于序列(InSequence)中的期望,在其后面的期望被使用后也会自动失效,这也是它不再“粘”的一种情形。

常见陷阱:以下循环会导致错误:

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

因为最新的期望是粘性的,第二次调用会直接命中最后一个期望并立即上限违规。正确的做法是结合 RetiresOnSaturation() 和 InSequence 来表达依次返回 10, 20, 30 … 的序列。

不感兴趣的调用

如果某个方法你不关心它是否被调用、调用多少次,最简单的方式是 不做任何期望。gMock 会允许该调用发生,但会在测试输出中发出一条“唠叨”(nagging)警告。要改变这种行为,可以将模拟对象标记为 NiceMock(抑制警告)或 StrictMock(将这种未预期的调用视为失败),详见官方文档。

小结

gMock 提供了一套丰富的 DSL,让你能够:

  • 用 MOCK_METHOD 宏快速生成模拟类。
  • 通过 EXPECT_CALL 声明期望,包括参数匹配、调用次数、动作。
  • 控制调用顺序,处理粘性期望和“不关心”的方法。

GMock 常用技巧

本文基于 gMock Cook Book 整理,涵盖日常开发中最常用的 gMock 技巧。本文基于 gMock Cook Book 整理,涵盖日常开发中最常用的 gMock 技巧。

一、创建 Mock 类

  1. 基本语法
    Mock 类使用 MOCK_METHOD 宏生成模拟方法:

    class MyMock {
    public:
     MOCK_METHOD(ReturnType, MethodName, (Args...));
     MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));
     MOCK_METHOD(返回类型, 方法名, (可变参数), (Specs...));
    };

    Specs 支持以下限定符:

  2. const — 模拟 const 方法
  3. override — 标记 override(建议)
  4. noexcept — 标记 noexcept
  5. Calltype(...) — 设置调用类型(Windows STDMETHODCALLTYPE 等)
  6. ref(&) / ref(&&) — 引用限定
  7. 处理未受保护的逗号
    MOCK_METHOD 遇到模板类型中的逗号(如 std::pair<int, bool>)会解析失败。
  8. 方案一:用括号包裹

    MOCK_METHOD((std::pair<int, bool>), GetPair, ());
    MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
  9. 方案二:定义类型别名

    using BoolAndInt = std::pair<int, bool>;
    MOCK_METHOD(BoolAndInt, GetPair, ());
  10. 模拟私有/受保护的方法
    MOCK_METHOD 必须放在 mock 类的 public 部分,无论原方法在基类中是什么访问级别。

    class MockFoo : public Foo {
    public:
     MOCK_METHOD(bool, Transform, (Gadget* g), (override));
     MOCK_METHOD(void, Resume, (), (override));      // 原为 protected
     MOCK_METHOD(int, GetTimeOut, (), (override));   // 原为 private
    };
  11. 模拟重载方法
    重载函数直接模拟即可。注意:如果只模拟部分重载版本,用 using Base::MethodName; 将其他版本引入作用域,避免隐藏警告。

    class MockFoo : public Foo {
    public:
     using Foo::Add;  // 引入基类的其他 Add 重载
     MOCK_METHOD(int, Add, (Element x), (override));
    };
  12. 模拟类模板
    语法与普通类相同:

    template <typename Elem>
    class MockStack : public StackInterface<Elem> {
    public:
     MOCK_METHOD(int, GetSize, (), (const, override));
     MOCK_METHOD(void, Push, (const Elem& x), (override));
    };
  13. 模拟非虚方法
    gMock 可以模拟非虚函数,但 mock 类与真实类不共享基类,需通过模板在编译时选择:

    class MockPacketStream {
    public:
     MOCK_METHOD(const Packet*, GetPacket, (size_t n), (const));
     MOCK_METHOD(size_t, NumberOfPackets, (), (const));
    };
  14. 模拟自由函数
    无法直接模拟自由函数。推荐方案:引入接口(抽象类),将自由函数调用封装在具体类中;或改用 std::function + MockFunction

二、Nice / Strict / Naggy

在 gMock (Google Mock) 中,NiceMockNaggyMock 和 StrictMock 是三种预定义的 Mock 行为模式,主要区别在于它们如何处理“无趣的调用” (uninteresting calls)。

“无趣的调用”:指测试代码调用了 Mock 对象的某个方法,但在当前测试用例中,你没有使用 EXPECT_CALL 为其设置任何期望(expectation)
模式中文翻译对“无趣调用”的反应特点
NiceMock友好模拟静默忽略 (ignores)最宽松,从不发出警告或报错。
NaggyMock唠叨模拟打印警告 (warns)默认模式,既提醒问题又不中断测试。
StrictMock严格模拟视为失败 (treats as failures)最严格,任何未预期的调用都会导致测试失败。

在 gMock 中,可以通过模板类 NiceMockNaggyMock 和 StrictMock 来包装你定义的 Mock 类。

using ::testing::NiceMock;
using ::testing::NaggyMock;
using ::testing::StrictMock;

// 假设你已经定义了 MockFoo 类
NiceMock<MockFoo> nice_foo;    // 创建一个“友好”的 mock 对象
NaggyMock<MockFoo> naggy_foo;  // 创建一个“唠叨”的 mock 对象
StrictMock<MockFoo> strict_foo; // 创建一个“严格”的 mock 对象


// 一个简单的示例
using ::testing::NiceMock;
TEST(...) {
    NiceMock<MockFoo> mock_foo;  // 继承 MockFoo 的构造函数
    EXPECT_CALL(mock_foo, mock_foo的 方法/函数名());
    // ...
}
  • 建议:大多数时候用 nice mock,开发调试时用 naggy(默认),仅在万不得已时用 strict

三、匹配器 (Matchers)

  1. 基本匹配

    EXPECT_CALL(foo, 方法/函数名(5));                    // 精确匹配
    EXPECT_CALL(foo, 方法/函数名(Ge(5)));                // >= 5
    EXPECT_CALL(foo, 方法/函数名("Hello", NotNull()));   // 第二个参数非 NULL
    EXPECT_CALL(foo, 方法/函数名(_, NotNull()));         // _ 匹配任意值
  2. 组合匹配器

    using ::testing::AllOf;
    using ::testing::AnyOf;
    using ::testing::Not;
    
    // 参数 > 5 且 != 10
    EXPECT_CALL(foo, 方法/函数名(AllOf(Gt(5), Ne(10))));
    // 参数不包含子串 "blah"
    EXPECT_CALL(foo, 方法/函数名(Not(HasSubstr("blah")), NULL));
  3. HasSubstr 是 gMock 专门为 std::stringstd::string_view 类型提供的字符串匹配器。作用是检查给定的字符串参数是否包含你指定的子字符串。

    组合器逻辑含义判定规则举例(参数为 8)
    AllOf(A, B)且(AND) B 都满足才通过AllOf(Gt(5), Lt(10)) ✅ 通过
    AnyOf(A, B)或(OR) B 任意一个满足就通过AnyOf(Gt(10), Lt(6)) ❌ 不通过
    Not(A)非(NOT)A 不满足才通过Not(Eq(8)) ❌ 不通过(因为是 8,被取反了)
  4. AllOf 最少要传 2 个参数,或者大于2个以上

比较匹配器(Comparison Matchers),专门用来比较数字或可比较的大小:

匹配器含义举例(参数实际为 x)
Ge(n)n 大于或等于x >= n
Ne(n)不等于x != n
Gt(n)大于x > n
Lt(n)小于x < n
Le(n)小于等于x <= n
Eq(n)等于x == n
记忆小技巧G 代表 Greater(更大),L 代表 Less(更小),e 代表 equal(等于),t 代表 than(比)。
  1. 根据参数执行不同操作

    EXPECT_CALL(foo, DoThis(_))                    // 默认情况
     .WillRepeatedly(Return('b'));
    EXPECT_CALL(foo, DoThis(Lt(5)))                // 更具体,覆盖前面的
     .WillRepeatedly(Return('a'));

    代码先用 DoThis(_) 设置了一个接收任何参数都返回 'b' 的默认兜底规则;当传入参数小于 5 时,更具体的 DoThis(Lt(5)) 会优先匹配并返回 'a';而当参数大于等于 5 时,则回退到默认规则返回 'b'。gMock 会根据匹配器的“最佳匹配”原则自动处理这种优先级,无需关心代码定义的先后顺序。

  2. 多参数整体匹配 — With()

    EXPECT_CALL(foo, InRange(Ne(0), _))
     .With(Lt());  // 第一个参数 < 第二个参数
    期待调用(EXPECT_CALL) foo的InRange 方法,要求第一个参数不能为 0,且两个参数必须满足左参数小于右参数,第二个参数本身不做任何数值限制。

用 Args<N1, N2...> 匹配任意选定的参数组合

EXPECT_CALL(foo, Blah)
    .With(AllOf(Args<0, 1>(Lt()), Args<1, 2>(Lt())));  // x < y < z
  1. 验证对象成员 — Field() / Property()

    // 匹配 x.number >= 3
    Field(&Foo::number, Ge(3))
    // 匹配 x.name() 以 "John " 开头
    Property(&Foo::name, StartsWith("John "))
    
    
    
    // 假设有个 User 类,里面有个公开字段 age,和一个方法 name()
    
    // 场景:Mock 一个验证函数,它接收 User 对象。
    // 我只关注:年龄 >= 18,名字不管!
    EXPECT_CALL(mock_validator, Validate(
     Field(&User::age, Ge(18))   // 👈 只看 age 字段,其他无视
    ));
    
    // 如果 age 是私有变量,只能通过 getAge() 获取:
    EXPECT_CALL(mock_validator, Validate(
     Property(&User::getAge, Ge(18)) // 👈 调用 getAge() 检查
    ));

    Field 和 Property 是 gMock 中用于匹配对象参数的匹配器,作用是在不关心整个对象内容的情况下,只单独提取并校验对象中的特定成员变量(Field)或方法返回值(Property,通常指 Getter)。这样做的好处是避免构造庞大且精确匹配的完整对象,让 EXPECT_CALL 的期望设定更简洁,并且当被 Mock 的类后续增加新字段时,已存在的测试用例不会因此失效,极大地提升了测试代码的可维护性。

  2. 验证指针指向的值 — Pointee()

    EXPECT_CALL(foo, Bar(Pointee(Ge(3))));  // 指向的值 >= 3
    // Pointee() 自动处理 NULL(视为匹配失败)
    // 嵌套使用:Pointee(Pointee(Lt(3)))

    Pointee 的作用是作为“解引用指针”匹配器,专门用于被测代码传入指针参数(如 int* 或智能指针)的场景,它能自动解引用该指针并校验其指向的实际值是否满足内部匹配器(如是否大于等于 3);同时它内置了空指针安全校验,若指针为 NULL 则直接判定匹配失败,从而避免测试代码因访问空指针而崩溃,嵌套使用时还支持校验多级指针指向的深层内容(如指向指针的指针)。

  3. 匹配容器 — ElementsAre() / UnorderedElementsAre()

    // 有序匹配,容器必须有 4 个元素
    EXPECT_CALL(mock, Foo(ElementsAre(1, Gt(0), _, 5)));
    
    // 无序匹配(适用于 unordered_map 等)
    EXPECT_CALL(mock, Foo(UnorderedElementsAre(1, Gt(0), _, 5)));
    
    // 关联容器用 Pair
    EXPECT_THAT(m, UnorderedElementsAre(Pair("a", 1), Pair("b", 2)));
  4. 匹配不可复制参数 — std::ref()

    EXPECT_CALL(mock_obj, Foo(Eq(std::ref(bar))));  // 保存引用而非副本
    // 注意:EXPECT_CALL 后不要修改 bar
  5. 自定义匹配器
  6. 简单场景用 Truly() 包装谓词:

    int IsEven(int n) { return n % 2 == 0; }
    EXPECT_CALL(foo, Bar(Truly(IsEven)));
  7. 复杂场景定义匹配器类:

    class BarPlusBazEqMatcher {
    public:
     using is_gtest_matcher = void;
     explicit BarPlusBazEqMatcher(int expected_sum) : expected_sum_(expected_sum) {}
     bool MatchAndExplain(const Foo& foo, std::ostream*) const {
         return (foo.bar() + foo.baz()) == expected_sum_;
     }
     void DescribeTo(std::ostream* os) const {
         *os << "bar() + baz() equals " << expected_sum_;
     }
     void DescribeNegationTo(std::ostream* os) const {
         *os << "bar() + baz() does not equal " << expected_sum_;
     }
    private:
     const int expected_sum_;
    };
    Matcher<Foo> BarPlusBazEq(int expected_sum) {
     return BarPlusBazEqMatcher(expected_sum);
    }
  8. 共享 Matcher
    Matcher 是引用计数的,复制高效,可重复使用:

    Matcher<int> in_range = AllOf(Gt(5), Le(10));
    EXPECT_CALL(foo, DoThis(in_range));
    EXPECT_CALL(bar, DoThat(in_range));

    将复杂的匹配逻辑定义为 Matcher<T> 变量,即可在多处 EXPECT_CALL 中重复使用,且底层采用引用计数确保复制高效,便于统一维护(符合 DRY 原则)。

四、设置预期

建议:默认使用 ON_CALL,仅在确实需要验证调用时才用 EXPECT_CALL

// 在测试夹具中设置通用行为
ON_CALL(mock, DoThis(_)).WillByDefault(Return('b'));

// 在具体测试中验证特定行为
EXPECT_CALL(mock, DoThis(5)).WillOnce(Return('a'));

过度使用 EXPECT_CALL 会使测试变得脆弱,实现变更时测试容易失败。

禁止调用

EXPECT_CALL(foo, Bar(_)).Times(0);  // 任何参数都不允许调用

允许某些调用,禁止其他

EXPECT_CALL(foo, Bar(5));                       // 只允许参数为 5
EXPECT_CALL(foo, Bar(Gt(10))).Times(AnyNumber());  // >10 任意次
// 其他参数的调用会报错

五、其他实用技巧

  1. 委托给 Fake 对象
    ...
  2. 委托给真实对象
    ...
  3. 委托给父类实现
    ...
  4. 简化长参数列表
    ...
  5. 将 Matcher 用作谓词
    ...

六、重要提醒

  1. 命名空间:gMock 位于 testing 命名空间,建议统一 using ::testing::Foo;
  2. Matcher 必须无副作用:gMock 不保证 matcher 的调用次数和时机
  3. 旧式宏MOCK_METHODn 系列已过时,推荐使用新的 MOCK_METHOD
  4. EXPECT_CALL 顺序:新的覆盖旧的,更具体的匹配应写在后面