
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.mdusing ::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命名空间中并作为ActionF类型的多态动作使用。默认动作Default Action即使你不写任何WillOnce()mock 函数也存在默认动作见 gmock_for_dummies.mdvoid函数直接返回bool函数返回false其他内置类型函数返回 0在 C 11 及以上若返回类型是“可默认构造”的具有默认构造函数默认动作是返回一个默认构造的值。当显式写了Times(n)而WillOnce()数量不足时多余的调用同样会回落到默认动作——例如int函数在WillOnce(Return(100))用完后会返回 0gmock_for_dummies.md。关键陷阱动作子句只求值一次EXPECT_CALL()语句对动作子句只求值一次即使动作会被执行多次gmock_for_dummies.mdint n 100; EXPECT_CALL(turtle, GetX()) .Times(4) .WillRepeatedly(Return(n)); // n 只执行一次结果并非依次返回 100、101、102…而是始终返回 100同理Return(new Foo)也只在设置期望时创建一次对象。若需要“每次调用都产生副作用”必须自定义 Action。二、返回一个值Returning a ValueAction说明Return()从一个voidmock 函数返回。Return(value)返回value。若value的类型与 mock 函数的返回类型不同会在设置期望的那一刻而非执行动作时转换为后者类型。ReturnArgN()返回第N个从 0 开始参数。ReturnNewT(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.htemplate typename T class ReturnRoundRobinAction { public: explicit ReturnRoundRobinAction(std::vectorT 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::vectorT values; size_t i 0; }; std::shared_ptrState state_ std::make_sharedState(); };两个值得注意的实现细节至少需要一个元素构造函数通过GTEST_CHECK_断言列表非空空列表会在运行期直接报错共享状态循环位置i保存在shared_ptrState中因此把同一个 Action 复制多份也不会丢失“下一次该返回哪个”的进度。其公开工厂函数支持初始化列表与std::vector两种入参形式gmock-actions.h。测试用例 gmock-actions_test.cc 分别验证了两种用法TEST(ReturnRoundRobinTest, WorksForInitList) { Actionint() ret ReturnRoundRobin({1, 2, 3}); ... } TEST(ReturnRoundRobinTest, WorksForVector) { std::vectordouble v {3.5, 2.0, 1.5}; Actiondouble() ret ReturnRoundRobin(v); ... }ReturnRef/ReturnRefOfCopy的使用场景ReturnRef(variable)适合返回对由 mock 持有、且生命周期稳定的成员的引用ReturnRefOfCopy(value)返回对 Action 内部一份拷贝的引用避免悬垂引用——因为引用指向的拷贝与 Action 同生命周期即使原value之后被销毁也安全。参数索引约定ReturnArgN()中的N是0 起始的参数索引ReturnArg0()返回第 1 个参数ReturnArg2()返回第 3 个参数。这条约定贯穿全文所有带N的动作与复合动作。三、副作用Side EffectsAction说明Assign(variable, value)将value赋给variable。DeleteArgN()delete第N个0 起始参数该参数必须是指针。SaveArgN(pointer)将第N个参数保存到*pointer。SaveArgPointeeN(pointer)将第N个参数所指向的值保存到*pointer。SetArgRefereeN(value)将value赋给第N个参数所引用的变量。SetArgPointeeN(value)将value赋给第N个参数所指向的变量。SetArgumentPointeeN(value)同SetArgPointeeN(value)。已废弃将在 v1.7.0 移除。SetArrayArgumentN(first, last)将源区间[first, last)中的元素拷贝到第N个参数指向的数组该参数可以是指针或迭代器。动作不接管源区间元素的所有权。SetErrnoAndReturn(error, value)将errno设为error并返回value。Throw(exception)抛出给定异常exception可以是任意可拷贝值。自 v1.1.0 起可用。底层实现佐证Assign的实现类是AssignActiongmock-actions.h核心是Perform中的一行*ptr_ value_;——它只做赋值不持有引用语义之外的状态SetErrnoAndReturn的实现类是SetErrnoAndReturnActiongmock-actions.h 附近用于模拟那些依赖errno的错误探测型 APISetArgPointeeN在测试中覆盖了多种入参形态gmock-actions_test.ccint、char字面量、宽字符、char*指针等例如ActionMyFunction a SetArgPointee1(2); // 第 2 个参数指向的变量被赋为 2 ActionMyFunction b SetArgPointee0(hi); // 支持字符串字面量实战示例模拟“回填”型 APIusing ::testing::_; using ::testing::SetArgPointee; using ::testing::SetArrayArgument; // 模拟一个“读取缓冲区并返回字节数”的接口 EXPECT_CALL(mock_reader, Read(_, _)) .WillOnce(DoAll(SetArgPointee1(42), // 回填输出参数 Return(1))); // 模拟将源区间 [first, last) 拷贝到第 0 个参数指向的数组 std::vectorint src {1, 2, 3}; EXPECT_CALL(mock, Fill(_)) .WillOnce(SetArrayArgument0(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)调用对象上的成员函数该成员函数不接收参数。InvokeArgumentN(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与按引用传参在InvokeArgumentN(...)中如果需要按引用传递某个参数需用std::ref()包裹using ::testing::InvokeArgument; ... InvokeArgument2(5, string(Hi), std::ref(foo))这条语句会调用 mock 函数的第 3 个参数一个函数/仿函数向它传入5与string(Hi)按值以及foo按引用。五、默认动作Default ActionAction说明DoDefault()执行默认动作由ON_CALL()指定或使用内置默认动作。DoDefault()让你在“先ON_CALL兜底、再WillOnce特化”的场景中显式回落默认行为。其实现类是 gmock-actions.h 中的DoDefaultAction——它通过模板类型转换操作符把DoDefault()转换为任意签名F的ActionF因此可以在任意返回类型的 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 ActionsAction说明DoAll(a1, a2, ..., an)每次调用依次执行动作a1到an并返回an的结果。前n - 1个子动作必须返回 void且它们收到的是参数的只读视图。IgnoreResult(a)执行动作a并忽略其结果a不能返回 void。WithArgN(a)把 mock 函数的第N个0 起始参数传给动作a并执行。WithArgsN1, N2, ..., Nk(a)把选中的0 起始参数传给动作a并执行。WithoutArgs(a)不传任何参数执行动作a。DoAll的源码级理解DoAll在源码中是一组偏特化模板gmock-actions.h 附近DoAllActionFinalAction处理“只有一个收尾动作”的情形DoAllActionInitialAction, 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(SaveArg0(last_key), // 先把入参存下来返回 void Return(config))); // 再返回结果IgnoreResult让“丢弃返回值”合法化当外层函数需要void动作、而内层动作有返回值时IgnoreResult负责“吞掉”结果using ::testing::IgnoreResult; using ::testing::Invoke; using ::testing::Return; Actionvoid() a IgnoreResult(Return(5)); // 多态动作 Actionvoid() b IgnoreResult(Invoke(ReturnOne)); // 单态动作测试 gmock-actions_test.cc 验证了IgnoreResult可用于多态动作、单态动作以及返回非默认可构造类类型的动作。WithArgs/WithArg重排、抽取参数WithArgsN1, N2, ..., Nk(a)是解决“mock 函数参数与被调用函数参数不一致”的标准手段详见 docs/gmock_cook_book.mdusing ::testing::WithArgs; using ::testing::Invoke; // IsVisibleInQuadrant1(bool x_is_positive, bool y_is_positive) // mock 的 Forward(x, y, visible) 只关心前两个参数 EXPECT_CALL(mock, Forward(_, _, _)) .WillOnce(WithArgs0, 2(Invoke(IsVisibleInQuadrant1)));要点索引是 0 起始的且可以重复、可以重排例如WithArgs2, 3, 3, 5(...)可以改变参数顺序例如WithArgs3, 2, 1(...)更简单时也可用WithArgN(a)只抽取单个参数如果只是想忽略某些参数也可以用第一节的Unused声明避免包一层WithArgs。七、定义自己的 ActionDefining 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_typemock 函数的函数类型与返回类型。从简单到参数化// 无参动作返回前两个参数之和 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.mdarguments指调用 mock 函数时传入的值parameters指实例化 Action 时传入的值。ACTION可以视为参数个数为 0 的ACTION_P且同名的ACTION_P、ACTION_P2可以按参数个数重载ACTION_P(Plus, a) { ... } ACTION_P2(Plus, a, b) { ... }限制参数/参数类型由于宏让编译器自动推导类型若需显式约束类型可在宏体内用类型转换或 gMock 的编译期断言StaticAssertTypeEqACTION(Foo) { int n arg0; // 约束 arg0 可转换为 int ... } ACTION_P(Bar, param) { ::testing::StaticAssertTypeEqconst char*, arg1_type(); // 约束 arg1 的类型 bool flag param; // 约束 param 可转换为 bool }进阶ACTION_TEMPLATE编写显式模板参数的 Action当动作需要无法从值参数推导出的显式模板参数时可使用ACTION_TEMPLATEgmock_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::getk(args)); } // 使用ActionNamet1, ..., tm(v1, ..., vn) // 例如DuplicateArg1, 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, k与typename, T。组合动作的约束回顾最后提醒两点与自定义动作相关的组合规则DoAll中的前导子动作必须返回 void且只能只读访问参数——需要修改参数时请使用SetArgPointee/SetArgReferee或把修改动作放在收尾位置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(SaveArg0(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(SetArgPointee0(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_TEMPLATE、WithArgs、Unused等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),仅供参考