GoogleTest gMock Actions 参考指南:从内置 Action 到自定义 Action 的完整实战手册
2026/9/19 23:57:19 网站建设 项目流程

GoogleTest gMock Actions 参考指南:从内置 Action 到自定义 Action 的完整实战手册

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

导读

在 GoogleTest 的 gMock 框架中,Action(动作)定义了 mock 函数被调用时应执行的“行为”:返回什么值、产生什么副作用、调用哪个真实函数。本文以 docs/reference/actions.md 为骨架,逐类讲解 gMock 提供的全部内置 Action(返回值、副作用、调用可调用对象、默认动作、复合动作),并结合 gmock-actions.h 源码与 gmock-actions_test.cc 测试用例,深入说明底层实现原理与实战陷阱,最后完整覆盖ACTION*宏体系,让你能够为复杂测试场景编写自定义 Action。读完本文,你将能在ON_CALL()/EXPECT_CALL()中熟练挑选、组合并自定义 Action,写出行为精确、可维护的 mock 测试代码。

前置阅读:如果你还不熟悉 mock 函数与期望(expectation)的基本写法,建议先阅读 docs/gmock_for_dummies.md 中的 “Actions: What Should It Do?” 一节(第 432 行起),本文会在该基础上做全面展开。


一、Action 是什么:在期望语句中扮演的角色

Action回答的问题是“mock 函数被调用时应该做什么”。Mock 对象本身没有真实实现,用户必须在期望中告诉它:

  • WillOnce()指定“第一次(或前几次)匹配时”执行的动作;
  • WillRepeatedly()指定“剩余调用次数”执行的动作;
  • ON_CALL()为匹配的调用设置默认动作。

例如(来自 gmock_for_dummies.md):

using ::testing::Return; // 恰好被调用三次,分别返回 100、200、300。 EXPECT_CALL(turtle, GetX()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillOnce(Return(300)); // 至少被调用两次:前两次返回 100、200,之后一直返回 300。 EXPECT_CALL(turtle, GetY()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillRepeatedly(Return(300));

所有内置 Action 均定义在::testing命名空间中,并作为Action<F>类型的多态动作使用。

默认动作(Default Action)

即使你不写任何WillOnce(),mock 函数也存在默认动作(见 gmock_for_dummies.md):

  • void函数直接返回;
  • bool函数返回false
  • 其他内置类型函数返回 0;
  • 在 C++ 11 及以上,若返回类型是“可默认构造”的(具有默认构造函数),默认动作是返回一个默认构造的值。

当显式写了Times(n)WillOnce()数量不足时,多余的调用同样会回落到默认动作——例如int函数在WillOnce(Return(100))用完后会返回 0(gmock_for_dummies.md)。

关键陷阱:动作子句只求值一次

EXPECT_CALL()语句对动作子句只求值一次,即使动作会被执行多次(gmock_for_dummies.md):

int n = 100; EXPECT_CALL(turtle, GetX()) .Times(4) .WillRepeatedly(Return(n++)); // n++ 只执行一次!

结果并非依次返回 100、101、102…,而是始终返回 100;同理Return(new Foo)也只在设置期望时创建一次对象。若需要“每次调用都产生副作用”,必须自定义 Action。


二、返回一个值(Returning a Value)

Action说明
Return()从一个voidmock 函数返回。
Return(value)返回value。若value的类型与 mock 函数的返回类型不同,会在设置期望的那一刻(而非执行动作时)转换为后者类型。
ReturnArg<N>()返回第N个(从 0 开始)参数。
ReturnNew<T>(a1, ..., ak)返回new T(a1, ..., ak)每次调用都会创建一个新对象
ReturnNull()返回空指针。
ReturnPointee(ptr)返回ptr所指向的值。
ReturnRef(variable)返回对variable的引用。
ReturnRefOfCopy(value)返回value的一份拷贝的引用;该拷贝与 Action 生命周期相同。
ReturnRoundRobin({a1, ..., ak})每次调用依次返回列表中的下一个ai,到列表末尾后从头开始循环。

ReturnRoundRobin的源码实现

ReturnRoundRobin是 gMock 中一个“多态动作”(可用于任意返回vector元素类型的函数),其核心实现位于 gmock-actions.h:

template <typename T> class ReturnRoundRobinAction { public: explicit ReturnRoundRobinAction(std::vector<T> values) { GTEST_CHECK_(!values.empty()) << "ReturnRoundRobin requires at least one element."; state_->values = std::move(values); } template <typename... Args> T operator()(Args&&...) const { return state_->Next(); } private: struct State { T Next() { T ret_val = values[i++]; if (i == values.size()) i = 0; // 到末尾后回到开头 return ret_val; } std::vector<T> values; size_t i = 0; }; std::shared_ptr<State> state_ = std::make_shared<State>(); };

两个值得注意的实现细节:

  1. 至少需要一个元素:构造函数通过GTEST_CHECK_断言列表非空,空列表会在运行期直接报错;
  2. 共享状态:循环位置i保存在shared_ptr<State>中,因此把同一个 Action 复制多份也不会丢失“下一次该返回哪个”的进度。

其公开工厂函数支持初始化列表std::vector两种入参形式(gmock-actions.h)。测试用例 gmock-actions_test.cc 分别验证了两种用法:

TEST(ReturnRoundRobinTest, WorksForInitList) { Action<int()> ret = ReturnRoundRobin({1, 2, 3}); ... } TEST(ReturnRoundRobinTest, WorksForVector) { std::vector<double> v = {3.5, 2.0, 1.5}; Action<double()> ret = ReturnRoundRobin(v); ... }

ReturnRef/ReturnRefOfCopy的使用场景

  • ReturnRef(variable):适合返回对由 mock 持有、且生命周期稳定的成员的引用;
  • ReturnRefOfCopy(value):返回对 Action 内部一份拷贝的引用,避免悬垂引用——因为引用指向的拷贝与 Action 同生命周期,即使原value之后被销毁也安全。

参数索引约定

ReturnArg<N>()中的N0 起始的参数索引:ReturnArg<0>()返回第 1 个参数,ReturnArg<2>()返回第 3 个参数。这条约定贯穿全文所有带<N>的动作与复合动作。


三、副作用(Side Effects)

Action说明
Assign(&variable, value)value赋给variable
DeleteArg<N>()deleteN个(0 起始)参数,该参数必须是指针。
SaveArg<N>(pointer)将第N个参数保存到*pointer
SaveArgPointee<N>(pointer)将第N个参数所指向的值保存到*pointer
SetArgReferee<N>(value)value赋给第N个参数所引用的变量
SetArgPointee<N>(value)value赋给第N个参数所指向的变量。
SetArgumentPointee<N>(value)SetArgPointee<N>(value)已废弃,将在 v1.7.0 移除。
SetArrayArgument<N>(first, last)将源区间[first, last)中的元素拷贝到第N个参数指向的数组(该参数可以是指针或迭代器)。动作不接管源区间元素的所有权。
SetErrnoAndReturn(error, value)errno设为error并返回value
Throw(exception)抛出给定异常,exception可以是任意可拷贝值。自 v1.1.0 起可用。

底层实现佐证

  • Assign的实现类是AssignAction(gmock-actions.h),核心是Perform中的一行*ptr_ = value_;——它只做赋值,不持有引用语义之外的状态;
  • SetErrnoAndReturn的实现类是SetErrnoAndReturnAction(gmock-actions.h 附近),用于模拟那些依赖errno的错误探测型 API;
  • SetArgPointee<N>在测试中覆盖了多种入参形态(gmock-actions_test.cc):intchar字面量、宽字符、char*指针等,例如:
Action<MyFunction> a = SetArgPointee<1>(2); // 第 2 个参数指向的变量被赋为 2 Action<MyFunction> b = SetArgPointee<0>("hi"); // 支持字符串字面量

实战示例:模拟“回填”型 API

using ::testing::_; using ::testing::SetArgPointee; using ::testing::SetArrayArgument; // 模拟一个“读取缓冲区并返回字节数”的接口 EXPECT_CALL(mock_reader, Read(_, _)) .WillOnce(DoAll(SetArgPointee<1>(42), // 回填输出参数 Return(1))); // 模拟将源区间 [first, last) 拷贝到第 0 个参数指向的数组 std::vector<int> src = {1, 2, 3}; EXPECT_CALL(mock, Fill(_)) .WillOnce(SetArrayArgument<0>(src.begin(), src.end()));

四、把函数、仿函数或 Lambda 当作 Action

下面用“callable”统称自由函数、std::function、仿函数(functor)与 lambda。

Action说明
f直接用可调用对象f作为动作:以传给 mock 函数的参数调用f
Invoke(f)以传给 mock 函数的参数调用f,其中f可以是全局/静态函数或仿函数。
Invoke(object_pointer, &class::method)以传给 mock 函数的参数在object_pointer上调用&class::method成员函数。
InvokeWithoutArgs(f)调用f(全局/静态函数或仿函数),f必须不接收任何参数
InvokeWithoutArgs(object_pointer, &class::method)调用对象上的成员函数,该成员函数不接收参数。
InvokeArgument<N>(arg1, arg2, ..., argk)k个参数调用 mock 函数的第N个(0 起始)参数,该参数必须是一个函数或仿函数。

被调用函数的返回值将作为该动作的返回值。

Unused忽略不关心的参数

为配合Invoke*()定义可调用对象时,可以将任何未使用的参数声明为Unused(类型由 gMock 提供):

using ::testing::Invoke; double Distance(Unused, double x, double y) { return sqrt(x*x + y*y); } ... EXPECT_CALL(mock, Foo("Hi", _, _)).WillOnce(Invoke(Distance));

这里mock.Foo的 3 个参数中只有第 2、3 个被用到,第一个用Unused占位,类型自动匹配。

关于所有权与基类类型的限制

Invoke(callback)InvokeWithoutArgs(callback)接管callback的所有权,因此callback必须是永久有效(不能是栈上临时对象)。此外,callback的类型必须是基类回调类型而非派生类型,否则无法编译:

BlockingClosure* done = new BlockingClosure; ... Invoke(done) ...; // 编译失败:类型不是基类类型 Closure* done2 = new BlockingClosure; ... Invoke(done2) ...; // 正确:基类指针

InvokeArgument与按引用传参

InvokeArgument<N>(...)中,如果需要按引用传递某个参数,需用std::ref()包裹:

using ::testing::InvokeArgument; ... InvokeArgument<2>(5, string("Hi"), std::ref(foo))

这条语句会调用 mock 函数的第 3 个参数(一个函数/仿函数),向它传入5string("Hi")(按值),以及foo(按引用)。


五、默认动作(Default Action)

Action说明
DoDefault()执行默认动作(由ON_CALL()指定,或使用内置默认动作)。

DoDefault()让你在“先ON_CALL兜底、再WillOnce特化”的场景中显式回落默认行为。其实现类是 gmock-actions.h 中的DoDefaultAction——它通过模板类型转换操作符把DoDefault()转换为任意签名FAction<F>,因此可以在任意返回类型的 mock 函数中使用。

注意:由于技术原因,DoDefault()不能用于复合动作(composite action)内部,强行使用会导致运行期错误。例如DoAll(DoDefault(), ...)是不允许的。

典型用法:

using ::testing::_; using ::testing::DoDefault; using ::testing::Return; // 兜底默认:任何参数都返回 -1 ON_CALL(mock, Lookup(_)).WillByDefault(Return(-1)); // 特化:仅对 "admin" 返回 1,其余走默认 EXPECT_CALL(mock, Lookup("admin")).WillOnce(Return(1)); EXPECT_CALL(mock, Lookup(_)).WillRepeatedly(DoDefault());

六、复合动作(Composite Actions)

Action说明
DoAll(a1, a2, ..., an)每次调用依次执行动作a1an,并返回an的结果。前n - 1个子动作必须返回 void,且它们收到的是参数的只读视图
IgnoreResult(a)执行动作a并忽略其结果,a不能返回 void。
WithArg<N>(a)把 mock 函数的第N个(0 起始)参数传给动作a并执行。
WithArgs<N1, N2, ..., Nk>(a)把选中的(0 起始)参数传给动作a并执行。
WithoutArgs(a)不传任何参数执行动作a

DoAll的源码级理解

DoAll在源码中是一组偏特化模板(gmock-actions.h 附近):DoAllAction<FinalAction>处理“只有一个收尾动作”的情形,DoAllAction<InitialAction, OtherActions...>递归处理“前导动作 + 其余动作”。这种设计保证了:

  • 只有最后一个子动作可以产生非 void 返回值;
  • 前导子动作只拿到参数的只读视图(const 引用),因此无法通过前导动作修改参数——需要“先改参数再返回”时,应把修改动作放到最后或使用SetArg*系列。

测试中对DoAll的移动语义也有覆盖:由于前导动作只能读取参数,从参数中移动内容(如std::unique_ptr形参)的DoAll组合是无法编译的(gmock-actions_test.cc)。

典型用法——先记录副作用再返回值:

using ::testing::DoAll; using ::testing::Invoke; using ::testing::Return; EXPECT_CALL(mock, GetConfig(_)) .WillOnce(DoAll(SaveArg<0>(&last_key), // 先把入参存下来(返回 void) Return(config))); // 再返回结果

IgnoreResult让“丢弃返回值”合法化

当外层函数需要void动作、而内层动作有返回值时,IgnoreResult负责“吞掉”结果:

using ::testing::IgnoreResult; using ::testing::Invoke; using ::testing::Return; Action<void()> a = IgnoreResult(Return(5)); // 多态动作 Action<void()> b = IgnoreResult(Invoke(ReturnOne)); // 单态动作

测试 gmock-actions_test.cc 验证了IgnoreResult可用于多态动作、单态动作以及返回非默认可构造类类型的动作。

WithArgs/WithArg:重排、抽取参数

WithArgs<N1, N2, ..., Nk>(a)是解决“mock 函数参数与被调用函数参数不一致”的标准手段(详见 docs/gmock_cook_book.md):

using ::testing::WithArgs; using ::testing::Invoke; // IsVisibleInQuadrant1(bool x_is_positive, bool y_is_positive) // mock 的 Forward(x, y, visible) 只关心前两个参数 EXPECT_CALL(mock, Forward(_, _, _)) .WillOnce(WithArgs<0, 2>(Invoke(IsVisibleInQuadrant1)));

要点:

  • 索引是 0 起始的,且可以重复、可以重排,例如WithArgs<2, 3, 3, 5>(...)
  • 可以改变参数顺序,例如WithArgs<3, 2, 1>(...)
  • 更简单时也可用WithArg<N>(a)只抽取单个参数;
  • 如果只是想忽略某些参数,也可以用第一节的Unused声明,避免包一层WithArgs

七、定义自己的 Action(Defining Actions)

说明
ACTION(Sum) { return arg0 + arg1; }定义一个动作Sum(),返回 mock 函数第 0、1 个参数之和。
ACTION_P(Plus, n) { return arg0 + n; }定义一个带参动作Plus(n),返回第 0 个参数与n之和。
ACTION_Pk(Foo, p1, ..., pk) { statements; }定义带k个参数的动作Foo(p1, ..., pk),执行给定语句。

ACTION*不能在函数或类内部使用,必须在文件作用域(或命名空间作用域)中定义。

宏体系内的自动符号

ACTION/ACTION_P*宏体内,gMock 自动提供以下符号(可从 gmock_cook_book.md 确认):

  • arg0,arg1, …:mock 函数的各参数(0 起始);
  • args:参数元组(供ACTION_TEMPLATE等使用);
  • arg0_type,arg1_type, …:各参数的类型;
  • param_type(或n_type等):ACTION_P*中参数的推导类型;
  • function_type/return_type:mock 函数的函数类型与返回类型。

从简单到参数化

// 无参动作:返回前两个参数之和 ACTION(Sum) { return arg0 + arg1; } // 单参动作:返回 arg0 + n ACTION_P(Add, n) { return arg0 + n; } // 使用:... WillOnce(Add(5)); // 返回参数 #0 + 5 // 多参动作:计算 (arg0, arg1) 到 (x, y) 的距离 ACTION_P2(ReturnDistanceTo, x, y) { double dx = arg0 - x; double dy = arg1 - y; return sqrt(dx*dx + dy*dy); } // 使用:... WillOnce(ReturnDistanceTo(5.0, 26.5));

注意术语区分(gmock_cook_book.md):arguments指调用 mock 函数时传入的值,parameters指实例化 Action 时传入的值。ACTION可以视为参数个数为 0 的ACTION_P,且同名的ACTION_PACTION_P2可以按参数个数重载:

ACTION_P(Plus, a) { ... } ACTION_P2(Plus, a, b) { ... }

限制参数/参数类型

由于宏让编译器自动推导类型,若需显式约束类型,可在宏体内用类型转换或 gMock 的编译期断言StaticAssertTypeEq

ACTION(Foo) { int n = arg0; // 约束 arg0 可转换为 int ... } ACTION_P(Bar, param) { ::testing::StaticAssertTypeEq<const char*, arg1_type>(); // 约束 arg1 的类型 bool flag = param; // 约束 param 可转换为 bool }

进阶:ACTION_TEMPLATE编写显式模板参数的 Action

当动作需要无法从值参数推导出的显式模板参数时,可使用ACTION_TEMPLATE(gmock_cook_book.md),它是ACTION/ACTION_P*的扩展:

ACTION_TEMPLATE(DuplicateArg, HAS_2_TEMPLATE_PARAMS(int, k, typename, T), AND_1_VALUE_PARAMS(output)) { *output = T(std::get<k>(args)); } // 使用:ActionName<t1, ..., tm>(v1, ..., vn) // 例如:DuplicateArg<1, std::string>(&out)

HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m)声明m个模板参数(m ∈ [1, 10]kind可为typename、整型常量或模板),AND_n_VALUE_PARAMS(p1, ..., p_n)声明n个值参数(n ∈ [0, 10])。注意多个模板参数之间用逗号分隔(如上例int, ktypename, T)。

组合动作的约束回顾

最后提醒两点与自定义动作相关的组合规则:

  1. DoAll中的前导子动作必须返回 void,且只能只读访问参数——需要修改参数时请使用SetArgPointee/SetArgReferee或把修改动作放在收尾位置;
  2. DoDefault()不能嵌套在复合动作内部使用。

八、完整实战:把 Action 组合起来

下面综合运用返回值、副作用、复合动作与自定义动作,模拟一个典型的缓存服务:

#include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::_; using ::testing::DoAll; using ::testing::Invoke; using ::testing::Return; using ::testing::ReturnRoundRobin; using ::testing::SaveArg; using ::testing::SetArgPointee; class Cache { public: virtual ~Cache() = default; virtual int Lookup(const std::string& key) = 0; virtual bool Put(const std::string& key, int value) = 0; virtual size_t Scan(const std::string* keys, size_t n) = 0; }; class MockCache : public Cache { public: MOCK_METHOD(int, Lookup, (const std::string&), (override)); MOCK_METHOD(bool, Put, (const std::string&, int), (override)); MOCK_METHOD(size_t, Scan, (const std::string*, size_t), (override)); }; // 文件作用域自定义动作(不能在函数/类内定义) ACTION_P2(LogThenReturn, key, value) { GTEST_LOG_(INFO) << "Lookup(" << key << ") -> " << value; return value; } TEST(CacheMockTest, CompositeActions) { MockCache mock; std::string last_key; // 1. 命中路径:记录入参并返回轮换值 EXPECT_CALL(mock, Lookup("a")) .WillOnce(DoAll(SaveArg<0>(&last_key), Return(10))) .WillRepeatedly(ReturnRoundRobin({10, 20, 30})); EXPECT_EQ(mock.Lookup("a"), 10); EXPECT_EQ(mock.Lookup("a"), 20); // 轮换到 20 EXPECT_EQ(mock.Lookup("a"), 30); // 轮换到 30 EXPECT_EQ(mock.Lookup("a"), 10); // 回到开头 EXPECT_EQ(last_key, "a"); // 2. 未命中路径:默认动作兜底 EXPECT_CALL(mock, Lookup("miss")).WillRepeatedly(Return(-1)); EXPECT_EQ(mock.Lookup("miss"), -1); // 3. 回填型接口:设置输出参数再返回 EXPECT_CALL(mock, Scan(_, _)) .WillOnce(DoAll(SetArgPointee<0>("cached"), Return(1u))); const std::string* out = nullptr; EXPECT_EQ(mock.Scan(out, 1), 1u); EXPECT_EQ(*out, "cached"); // 4. 自定义动作 EXPECT_CALL(mock, Lookup("k")) .WillOnce(LogThenReturn("k", 7)); EXPECT_EQ(mock.Lookup("k"), 7); }

九、相关文档与进一步阅读

  • 动作在期望语句中的完整语法(WillOnce/WillRepeatedly/ON_CALL的搭配与基数推导规则):docs/gmock_for_dummies.md
  • 匹配器(Matcher)参考——在ON_CALL/EXPECT_CALL中筛选参数与断言值: docs/reference/matchers.md
  • 自定义动作的深入教程(ACTION_TEMPLATEWithArgsUnused等):docs/gmock_cook_book.md
  • 单页速查表(含 Action 与 Cardinality 列表):docs/gmock_cheat_sheet.md
  • 内置 Action 与自定义 Action 的源码实现:googlemock/include/gmock/gmock-actions.h
  • 覆盖本文绝大多数动作的测试用例:googlemock/test/gmock-actions_test.cc

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询