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:
- 公开继承
Turtle。 - 在
public:区域内,对每一个虚函数使用MOCK_METHOD宏。 - 宏的参数依次为:返回类型、方法名、参数列表、修饰符(如
const、override)。
修饰符参数需要外加括号。
对于 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。 另一种做法:为外部依赖引入一个自己拥有的薄适配层,然后针对适配器接口写模拟。这会增加一些前期工作,但长期看可以让你的代码更贴近领域需求,降低耦合。
在测试中使用模拟类
基本工作流:
- 导入 gMock 名称(例如
using ::testing::AtLeast;)。 - 创建模拟对象。
- 在模拟对象上设置期望(调用次数、参数、行为等)。
- 运行被测代码,使用 googletest 断言检查结果。如果调用不符合期望,gMock 会立刻失败并给出详细信息。
- 测试结束时,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, _));实际上 100、50 也是匹配器,相当于 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 类
基本语法
Mock 类使用MOCK_METHOD宏生成模拟方法:class MyMock { public: MOCK_METHOD(ReturnType, MethodName, (Args...)); MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...)); MOCK_METHOD(返回类型, 方法名, (可变参数), (Specs...)); };Specs支持以下限定符:const— 模拟 const 方法override— 标记 override(建议)noexcept— 标记 noexceptCalltype(...)— 设置调用类型(Windows STDMETHODCALLTYPE 等)ref(&)/ref(&&)— 引用限定- 处理未受保护的逗号
MOCK_METHOD遇到模板类型中的逗号(如std::pair<int, bool>)会解析失败。 方案一:用括号包裹
MOCK_METHOD((std::pair<int, bool>), GetPair, ()); MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));方案二:定义类型别名
using BoolAndInt = std::pair<int, bool>; MOCK_METHOD(BoolAndInt, GetPair, ());模拟私有/受保护的方法
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 };模拟重载方法
重载函数直接模拟即可。注意:如果只模拟部分重载版本,用using Base::MethodName;将其他版本引入作用域,避免隐藏警告。class MockFoo : public Foo { public: using Foo::Add; // 引入基类的其他 Add 重载 MOCK_METHOD(int, Add, (Element x), (override)); };模拟类模板
语法与普通类相同:template <typename Elem> class MockStack : public StackInterface<Elem> { public: MOCK_METHOD(int, GetSize, (), (const, override)); MOCK_METHOD(void, Push, (const Elem& x), (override)); };模拟非虚方法
gMock 可以模拟非虚函数,但 mock 类与真实类不共享基类,需通过模板在编译时选择:class MockPacketStream { public: MOCK_METHOD(const Packet*, GetPacket, (size_t n), (const)); MOCK_METHOD(size_t, NumberOfPackets, (), (const)); };- 模拟自由函数
无法直接模拟自由函数。推荐方案:引入接口(抽象类),将自由函数调用封装在具体类中;或改用std::function+MockFunction
二、Nice / Strict / Naggy
在 gMock (Google Mock) 中,NiceMock、NaggyMock 和 StrictMock 是三种预定义的 Mock 行为模式,主要区别在于它们如何处理“无趣的调用” (uninteresting calls)。
“无趣的调用”:指测试代码调用了 Mock 对象的某个方法,但在当前测试用例中,你没有使用 EXPECT_CALL 为其设置任何期望(expectation)。| 模式 | 中文翻译 | 对“无趣调用”的反应 | 特点 |
|---|---|---|---|
| NiceMock | 友好模拟 | 静默忽略 (ignores) | 最宽松,从不发出警告或报错。 |
| NaggyMock | 唠叨模拟 | 打印警告 (warns) | 默认模式,既提醒问题又不中断测试。 |
| StrictMock | 严格模拟 | 视为失败 (treats as failures) | 最严格,任何未预期的调用都会导致测试失败。 |
在 gMock 中,可以通过模板类 NiceMock、NaggyMock 和 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)
基本匹配
EXPECT_CALL(foo, 方法/函数名(5)); // 精确匹配 EXPECT_CALL(foo, 方法/函数名(Ge(5))); // >= 5 EXPECT_CALL(foo, 方法/函数名("Hello", NotNull())); // 第二个参数非 NULL EXPECT_CALL(foo, 方法/函数名(_, NotNull())); // _ 匹配任意值组合匹配器
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));HasSubstr 是 gMock 专门为
std::string和std::string_view类型提供的字符串匹配器。作用是检查给定的字符串参数是否包含你指定的子字符串。组合器 逻辑含义 判定规则 举例(参数为 8) AllOf(A, B)且(AND) A 且 B 都满足才通过 AllOf(Gt(5), Lt(10))✅ 通过AnyOf(A, B)或(OR) A 或 B 任意一个满足就通过 AnyOf(Gt(10), Lt(6))❌ 不通过Not(A)非(NOT) A 不满足才通过 Not(Eq(8))❌ 不通过(因为是 8,被取反了)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(比)。
根据参数执行不同操作
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 会根据匹配器的“最佳匹配”原则自动处理这种优先级,无需关心代码定义的先后顺序。
多参数整体匹配 — 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验证对象成员 — 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 的类后续增加新字段时,已存在的测试用例不会因此失效,极大地提升了测试代码的可维护性。验证指针指向的值 — Pointee()
EXPECT_CALL(foo, Bar(Pointee(Ge(3)))); // 指向的值 >= 3 // Pointee() 自动处理 NULL(视为匹配失败) // 嵌套使用:Pointee(Pointee(Lt(3)))Pointee的作用是作为“解引用指针”匹配器,专门用于被测代码传入指针参数(如int*或智能指针)的场景,它能自动解引用该指针并校验其指向的实际值是否满足内部匹配器(如是否大于等于 3);同时它内置了空指针安全校验,若指针为 NULL 则直接判定匹配失败,从而避免测试代码因访问空指针而崩溃,嵌套使用时还支持校验多级指针指向的深层内容(如指向指针的指针)。匹配容器 — 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)));匹配不可复制参数 — std::ref()
EXPECT_CALL(mock_obj, Foo(Eq(std::ref(bar)))); // 保存引用而非副本 // 注意:EXPECT_CALL 后不要修改 bar- 自定义匹配器
简单场景用
Truly()包装谓词:int IsEven(int n) { return n % 2 == 0; } EXPECT_CALL(foo, Bar(Truly(IsEven)));复杂场景定义匹配器类:
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); }共享 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 任意次
// 其他参数的调用会报错五、其他实用技巧
- 委托给 Fake 对象
... - 委托给真实对象
... - 委托给父类实现
... - 简化长参数列表
... - 将 Matcher 用作谓词
...
六、重要提醒
- 命名空间:gMock 位于
testing命名空间,建议统一using ::testing::Foo; - Matcher 必须无副作用:gMock 不保证 matcher 的调用次数和时机
- 旧式宏:
MOCK_METHODn系列已过时,推荐使用新的MOCK_METHOD EXPECT_CALL顺序:新的覆盖旧的,更具体的匹配应写在后面
