与自定义动作完整指南)
GoogleTest Actions 权威参考gMock 内置动作Built-in Actions与自定义动作完整指南【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest本指南以 GoogleTest 官方参考文档 docs/reference/actions.md 为骨架深入拆解 gMock 中动作Action这一核心概念动作规定了当 mock 函数被调用时它应当做什么。文章逐一覆盖返回值型、副作用型、可调用对象型、默认动作、组合动作与ACTION*自定义动作等全部类别并结合gmock-actions.h、gmock-more-actions.h的实现与其单元测试进行源码级佐证。读完本文你将能够像查字典一样熟练选取、组合甚至亲手编写 gMock 动作让EXPECT_CALL/ON_CALL真正做到指哪打哪。基础概念速览动作是配合 Actions: What Should It Do? 章节中WillOnce()/WillRepeatedly()/WillByDefault()使用的行为单元。一个 mock 对象本身没有真实实现用户通过期望expectation 动作action告知框架某方法被调用时该做什么。gMock 的所有内置动作都定义在::testing命名空间下这意味着在你的测试代码中既可以using ::testing::Return;单独引入也可以直接使用testing::Return(...)全限定名。一、返回值动作Returning a Valuemock 方法最常见的行为就是返回某个值。gMock 提供以下返回值动作动作说明Return()从一个返回void的 mock 函数中返回。Return(value)返回value。若value的类型与 mock 函数的返回类型不同value在设置期望的那一刻被转换为后者而不是在动作执行时才转换。ReturnArgN()返回第N个从 0 开始参数。ReturnNewT(a1, ..., ak)返回new T(a1, ..., ak)每次调用都创建一个新对象。ReturnNull()返回空指针。ReturnPointee(ptr)返回ptr所指向的值。ReturnRef(variable)返回对variable的引用。ReturnRefOfCopy(value)返回对value的一份副本的引用这份副本与动作同生命周期。ReturnRoundRobin({a1, ..., ak})每次调用依次返回列表中的下一个ai到达末尾后从头开始循环。1.1 深入Return(value)为何转换发生在设置期望时文档特别强调Return(value)中value与返回类型的转换是急切eager完成的而非动作执行时才转换。这一语义在 gmock-actions.h 的ReturnAction实现中得到了精确落实构造阶段ReturnAction(R value)保存原始值在转换为ActionU(Args...)/OnceActionU(Args...)时模板参数以std::is_convertibleR, U、std::is_move_constructibleU或std::is_copy_constructibleU作为硬性约束内部Impl::State在构造时即通过value(ImplicitCast_U(input_value))预先把结果类型U实例化出来并保存在堆上。源码注释揭示了一个关键陷阱若返回类型是类引用类型例如 mock 返回std::string_view而Return传入std::string如果延迟到执行期再转换源字符串可能在动作执行前已被销毁导致悬垂引用。因此 gMock 在 2009 年的提交中就把转换提前到动作定型时完成。这也提醒使用者传给Return()的值应当能在设置期望后安全存活。1.2ReturnRefvsReturnRefOfCopy引用的生命周期差异ReturnRef(variable)只是把引用转发出去不复制因此variable必须在 mock 被调用时仍然存活否则行为未定义它要求参数是非常量左值源码中甚至显式delete了右值重载ReturnRef(R) delete从编译期就拦截了返回对临时量引用的误用。而ReturnRefOfCopy(value)会把值复制一份并保存在动作对象内部返回的是指向这份副本的引用因此即使value在设置期望后立即销毁也没有问题——前提是动作本身存活副本与动作同生命周期。相应地ReturnRefOfCopy的实现内部直接持有值成员见ReturnRefOfCopyActiongmock-actions.h并在编译期用static_assert之类的检查捕获本应使用Return却误用ReturnRefOfCopy的场景。1.3ReturnArgN、ReturnNewT、ReturnPointee与智能指针ReturnArgN()的实现利用std::getk(std::forward_as_tuple(...))把第N个参数原样返回ReturnArgAction因此参数类型与返回类型必须兼容且越界访问在编译期通过enable_if(k sizeof...(Args))被拦截。ReturnNewT(a1, ..., ak)每次被调用都会执行new T(unpacked_params...)ReturnNewAction适用于每次调用需要全新对象的场景。测试ReturnNewTest.Unarygmock-actions_test.cc验证了它可用于返回裸指针与指向 const 的指针。ReturnPointee(ptr)返回*pointerReturnPointeeAction当指针所指向对象内容变化时后续调用会返回最新值。ReturnNull()是多态动作可同时用于裸指针与智能指针返回函数——测试ReturnNullTest.WorksInSmartPointerReturningFunction证实了这一点。1.4ReturnRoundRobin循环出值ReturnRoundRobin({a1, ..., ak})适用于按照预定序列反复循环返回的场景如依次返回1,2,3,1,2,3,...。其实现ReturnRoundRobinAction在构造时通过GTEST_CHECK_(!values.empty())校验至少需要一个元素并将状态封装在shared_ptrState中含std::vectorT values与游标size_t iNext()在取到末尾时重置为 0 形成环。因为状态被共享动作被复制后仍能维持连续计数。测试ReturnRoundRobinTest.WorksForInitList与WorksForVectorgmock-actions_test.cc分别覆盖了初始化列表与std::vector两种构造方式。二、副作用动作Side Effects除了返回值动作还可用于修改外部状态传出参数、被测试系统的全局变量等动作说明Assign(variable, value)把value赋给variable。DeleteArgN()delete第N个从 0 开始参数该参数必须是指针。SaveArgN(pointer)通过拷贝赋值把第N个参数保存到*pointer。SaveArgByMoveN(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 起可用。2.1 参数写入族把读参数变成写参数SetArgRefereeN(value)写入的是引用参数本身T典型场景是被 mock 的输出型引用参数应当被填上某个值。SetArgPointeeN(value)/SaveArgPointeeN(pointer)写入的是指针参数指向的内存二者一个往指针参数里写、一个把指针参数指向的内容读走。SaveArgN(pointer)按拷贝保存参数值SaveArgByMoveN(pointer)按移动保存适用于不可拷贝move-only的参数类型见 SaveArgByMoveAction 及工厂函数 SaveArgByMove。这些动作在源码中统一以std::getN(std::tie(args...))取出第N个参数后解引用写入。文档特别标注SetArgumentPointee名称已废弃并将被移除请直接使用SetArgPointee——当前仓库 gmock-actions.h 仍同时保留两个函数入口以便平滑迁移但新代码一律写SetArgPointee。SetArrayArgumentN(first, last)的循环实现SetArrayArgumentAction会从first遍历到last一边迭代源区间一边递增目标位置等价于std::copy(first, last, 目标起始)非常适合为缓冲区、C 风格数组批量填充测试数据。2.2Assign、DeleteArg、SetErrnoAndReturn的工程语义Assign(variable, value)在多线程/状态机测试中常用于某调用发生后翻转开关变量。AssignActiongmock-actions.h直接持有目标指针并执行*ptr_ value_属于典型副作用型动作。DeleteArgN()用于本应消费指针的所有权的场景。其实现DeleteArgAction对非指针参数的重载带有[[deprecated]]告警提示参数可能已迁移为智能指针应移除此动作。SetErrnoAndReturn(error, value)专门用于模拟read/write/ioctl等系统调用与 libc 函数的错误路径——既设置errno又返回-1。实现SetErrnoAndReturnAction的Perform()先执行errno errno_;再返回结果。注意平台限制该动作整体被#ifndef GTEST_OS_WINDOWS_MOBILE包裹即 Windows Mobile 平台不可用测试 gmock-actions_test.cc 的SetErrnoAndReturnTest覆盖了返回int、指针及可兼容类型三种情形。2.3Throw的使用前提Throw(exception)动作在 ThrowAction 中通过模板转换运算符适配任意返回类型运行时抛出的是exception的拷贝。源码将其放在#if GTEST_HAS_EXCEPTIONS保护之下也就是说只有编译器启用了异常C 异常打开时该动作才可用否则用它会导致编译失败或需要配合 gtest 的异常相关死亡测试机制使用。三、把函数、仿函数或 Lambda 当作动作Using a Function, Functor, or LambdagMock 允许调用一个可调用对象来替代内联返回值逻辑。下表中callable可调用对象泛指自由函数、std::function、仿函数functor或 lambda动作说明f以传给 mock 函数的参数调用ff为任意 callable。Invoke(f)以传给 mock 函数的参数调用ff可以是全局/静态函数或仿函数。Invoke(object_pointer, class::method)以传给 mock 函数的参数在对象上调用成员方法。InvokeWithoutArgs(f)调用f全局/静态函数或仿函数f必须不接收任何参数。InvokeWithoutArgs(object_pointer, class::method)调用对象上不接收任何参数的成员方法。InvokeArgumentN(arg1, arg2, ..., argk)调用 mock 函数的第N个从 0 开始参数——它必须是一个函数或仿函数——并向其传入给定的k个参数。除InvokeArgument外被调用函数的返回值将作为整个动作的返回值。3.1 直接用 callable 作动作与Invoke家族表中最上面一行f指的是当期望设定时直接写函数/仿函数/lambda 的名字不带Return包裹gMock 的ActionF会通过可调用对象兼容性检测接受它。这使得测试可以写出.WillOnce(MyFunctor{...})之类高度内聚的表达式。Invoke(f)、Invoke(obj, Cls::m)、InvokeWithoutArgs(...)是显式版本。成员方法版在底层被包装为(obj_ptr-*method_ptr)(std::forwardArgs(args)...)见 InvokeMethodAction调用时 mock 收到的参数会被无缝转发给真实方法——这经常用来把 mock 调用委托给一个真实的辅助对象。测试 gmock-more-actions_test.cc 中的InvokeTest系列Nullary/Unary/…/FunctionThatTakes10Arguments覆盖了 0 至 10 个参数的自由函数、成员方法、仿函数等情形可作为调用形态的完整参照。3.2Unused忽略不想处理的参数当自定义 callable 的形参个数少于 mock 函数时可用::testing::Unused声明我不关心的参数using ::testing::Invoke; using ::testing::Unused; double Distance(Unused, double x, double y) { return sqrt(x*x y*y); } ... EXPECT_CALL(mock, Foo(Hi, _, _)).WillOnce(Invoke(Distance));上面的Distance虽然只用到x、y但因为第一个参数声明为Unused它可以同时服务于形参签名不同的多个 mock 方法。源码中Unused被定义为internal::IgnoredValue的别名gmock-actions.h而IgnoredValue可从任意值隐式构造——这正是它能在不同签名间复用的原理。InvokeTest.FunctionWithUnusedParameters与InvokeTest.MethodWithUnusedParameters就是这一用法的实测样例。3.3Invoke的回调所有权约束文档特别提醒Invoke(callback)与InvokeWithoutArgs(callback)会接管callback的所有权且要求其为永久对象permanentcallback的类型必须是基类回调类型而非派生类型否则无法通过编译。原文档给出的例子如下BlockingClosure* done new BlockingClosure; ... Invoke(done) ...; // 这无法通过编译 Closure* done2 new BlockingClosure; ... Invoke(done2) ...; // 这可以正常工作。原因在于gMock 需要按基类类型统一持有并最终释放该回调若传入派生类指针编译器无法确定正确的所有权管理类型。这一约束本质上服务于由 mock 生命周期托管回调的设计。3.4InvokeArgumentN(...)回调参数的反向调用InvokeArgument是Invoke的反向操作——它不去调用某个已知函数而是调用 mock 收到的第N个参数该参数本身是一个函数/仿函数并把用户预置的实参传给它。典型场景是模拟回调型 API被测代码把回调传进来而你要验证/驱动这个回调。using ::testing::InvokeArgument; ... InvokeArgument2(5, string(Hi), std::ref(foo))上述代码会调用 mock 函数的第 2 个参数一个 3 元 callable以值方式传给它5和string(Hi)以引用方式传给它foo。引用传参规则务必注意InvokeArgument的参数默认按值传递若某个参数需要按引用传递必须用std::ref()包裹。此外文档强调一个隐蔽细节——若被调用方按引用接收参数但调用方忘了用std::ref()则它收到的是临时拷贝的引用而非原始对象这会让副作用落空。实现层面InvokeArgument定义在 gmock-more-actions.h其InvokeArgumentAction把N限定为enable_if(index sizeof...(Args))通过内部的FlatTuple保存实参并在执行期取出第N个 callable 加以调用。测试InvokeArgumentTest.Function5、Functor1MoveOnly、OneShotFunctor1等gmock-more-actions_test.cc验证了它可驱动空参函数、普通仿函数、move-only 仿函数与一次性one-shot仿函数等类型。四、默认动作Default Action动作说明DoDefault()执行默认动作由ON_CALL()设定或 gMock 的内置默认动作。DoDefault()通常用在EXPECT_CALL(...).WillRepeatedly(DoDefault())的组合里表达这条期望只关心参数匹配/调用次数实际行为交给默认实现——最常见的就是配合ON_CALL()预先设定的桩行为或在 mock 方法需要调用真实对象时见 gMock Cookbook 中的Delegating Calls to a Real Object。两个需要牢记的约束均可从源码与测试验证DoDefault()不能用在组合动作内部例如DoAll(x, DoDefault())。文档将其归因于技术原因并明确说明强行使用会在运行时报错。测试 gmock-actions_test.cc 的DoDefaultDeathTest.DiesIfUsedInCompositeAction以死亡测试断言了DoAll(VoidFunc, DoDefault())触发终止。DoDefault()不能放在ON_CALL().WillByDefault()中否则构成无限递归。同一文件中的DoDefaultTest.CannotBeUsedInOnCall死亡测试验证了这一点。另外要理解DoDefault()时内置默认值的语义当 mock 函数返回内建类型或指针时即使没有任何动作gMock 也会提供默认行为——void函数直接返回、bool返回false、数值返回 0、指针返回nullptrC11 起若返回类型可默认构造则默认返回默认构造的值。这个逻辑由 BuiltInDefaultValueGetter 实现类型没有内置默认值时Get()会断言失败并终止进程。因此DoDefaultTest.ReturnsBuiltInDefaultValueByDefault与DoDefaultTest.ReturnsUserSpecifiedPerTypeDefaultValueWhenThereIsOne等测试分别验证了无默认动作与用户通过DefaultValueT::Set()指定默认值两种路径。DoDefaultAction本身只是一层极薄的多态包装gmock-actions.h真正的默认值解析发生在动作执行时。五、组合动作Composite Actions组合动作用于把多个简单动作串成一条执行链或对动作入参做裁剪/重排动作说明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。5.1DoAll经典先副作用、后返回值组合DoAll是编写 mock 行为的瑞士军刀。最经典的用法是先写传出参数、再返回状态码EXPECT_CALL(mock, Read(_, _)) .WillOnce(DoAll(SetArgPointee0(buffer), SetArgPointee1(bytes_read), Return(0)));源码层面DoAll 工厂 的注释与实现共同说明了两条硬性规则除最后一个动作外其余子动作的返回值类型必须可忽略即返回void或可被包装为忽略值前面的子动作看到的是参数的只读视图——这符合用组合动作传值、用专用动作写参的分工哲学也避免了多个写动作间的执行顺序歧义。从注释与测试可推断若违反 void 约束通常会在编译期因返回类型不兼容而失败。DoAll的单参数重载已被标记废弃提示避免对单个动作使用DoAll见 gmock-actions.h。5.2WithArg/WithArgs重排与挑选参数WithArgsN1, N2, ..., Nk(action)允许你自由挑选、重排甚至重复使用mock 参数去喂给内层动作。例如当内层函数需要的参数顺序与 mock 签名不一致时using ::testing::WithArgs; EXPECT_CALL(mock, IsPointVisible(_, _)) .WillOnce(WithArgs1, 0(Invoke(MyCompare))); // 调换参数顺序单参数选取用WithArgN无s。内层动作不要求是Invoke——任何单参/多参动作皆可。参数可重复如WithArgs2, 3, 3, 5(...)也可只挑一部分如WithArgs0, 1, 3(Ternary)。当内层动作的参数比选出的参数少时多余参数会被丢弃。实现上WithArg/WithArgs/WithoutArgs都归一化到模板类WithArgsActiongmock-actions.hWithoutArgs本质上是0 个参数被选出的特例gmock-actions.h。测试 gmock-actions_test.cc 的WithArgsTest系列非常系统OneArg/TwoArgs/TenArgs覆盖参数数量边界RepeatedArguments验证WithArgs1, 1, 1, 1(SumOf4)这样的重复选取ReversedArgumentOrder验证WithArgs1, 0颠倒顺序NonInvokeAction则证明内层动作不必是InvokeReturnReference甚至允许内层动作返回引用。5.3IgnoreResult的定位IgnoreResult(a)的语义是执行但丢掉结果适用于你想复用某个有返回值却被当作纯副作用使用的函数或动作同时又能把它塞进要求 void 的上下文例如放在DoAll的非末位。实现层面IgnoreResultAction会把内层函数包装为忽略返回值的形式参见MakeResultIgnoredValue机制gmock-actions.h 与 internal 工具头 gmock-internal-utils.h。六、自定义动作Defining Actions当内置动作无法覆盖业务语义时gMock 提供了ACTION*宏家族来定义命名参数化动作宏说明ACTION(Sum) { return arg0 arg1; }定义动作Sum()返回 mock 函数第 0、1 个参数之和。ACTION_P(Plus, n) { return arg0 n; }定义动作Plus(n)返回 mock 函数第 0 个参数与n之和。ACTION_Pk(Foo, p1, ..., pk) { statements; }定义带k个参数的动作Foo(p1, ..., pk)执行给定语句。在宏体内mock 函数的参数以arg0、arg1、…、arg9最多 10 个直接可用并附带类型别名arg0_type、arg1_type…同时还能访问args完整参数元组与args_type。例如测试文件 gmock-actions_test.cc 中就用ACTION(ReturnArity) { return std::tuple_size_vargs_type; }定义了一个返回 mock 函数参数个数的动作并通过Actionint(int)(ReturnArity()).Perform(std::make_tuple(0))直接验证。6.1 宏到类的展开机制ACTION(name)会在编译期生成名为nameAction的类而ACTION_P/ACTION_Pk生成nameActionP/nameActionPk见 gmock-actions.h 及宏内部实现GMOCK_INTERNAL_ACTIONgmock-actions.h参数的模板类型别名自动生成GMOCK_ACTION_TYPENAME_PARAMS_因而使用ACTION_P(Plus, n)后可在函数体重写n_type以表达与参数类型相关的逻辑每个ACTION_P变体在构造时通过std::forwardparam##_type完美转发保存参数对应GMOCK_ACTION_INIT_PARAMS_支持 move-only 参数类型生成的动作类带template typename F operator ActionF()转换运算符因此能自动适配不同签名的 mock 函数——这正是宏定义动作天然多态的根源ACTION()系列是模板展开实现因此在类内部或函数内部无法定义。使用位置约束重要ACTION*宏不能在函数或类内部使用——文档明确列出此限制。其根因也写在头文件注释中gmock-actions.h宏生成的代码是模板C 不允许在局部类中声明模板但这并不妨碍在局部作用域直接使用普通 lambda 或仿函数作为动作。6.2 更底层的自定义途径进阶头文件注释gmock-actions.h给出了一条工程建议当某个自定义动作会被大量复用时优先考虑实现ActionInterface或使用MakePolymorphicAction()而非宏。宏虽然开发效率高但对mock 函数参数类型与动作参数类型的控制粒度较粗编译错误信息不够友好而ActionInterface方案允许按参数类型重载。实现ActionInterface需要两件套// 1. 一个实现 ActionInterface 的类实现 Perform() // 2. 一个返回 Action 对象的工厂函数核心接口定义在 gmock-actions.hActionInterfaceF暴露Result Perform(const ArgumentTuple args)其中ArgumentTuple是 mock 函数参数的std::tuple。测试 gmock-actions_test.cc 中的MyActionImpl与IsNotZero分别演示了实现ActionInterface后直接构造Action以及配合MakeAction()使用的两种方式。此外PolymorphicActiongmock-actions.h让你仅凭一个带template Perform()的拷贝实现类就能生成可适配任意签名的动作Return()、ReturnNull()等多态内置动作正是由此机制提供。七、总结动作的选取方法论面对一个 mock 调用可按下述决策链快速锁定动作只想返回值用Return引用用ReturnRef注意被引用变量生命周期需要独立副本引用用ReturnRefOfCopy序列/循环值用ReturnRoundRobin返回参数之一用ReturnArg返回堆上新对象用ReturnNew空指针用ReturnNull。想改外部状态写引用参数用SetArgReferee写指针目标用SetArgPointee别再用废弃的SetArgumentPointee批量拷贝到数组用SetArrayArgument读走参数用SaveArg/SaveArgPointee/SaveArgByMove直接删参数用DeleteArg模拟系统调用失败用SetErrnoAndReturn想抛异常用Throw需启用 C 异常。想把调用转交给已有逻辑自由函数/仿函数/lambda 用Invoke或直接写 callable成员函数用Invoke(obj, Cls::m)无参版本用InvokeWithoutArgs调用传入的回调用InvokeArgument签名不匹配的参数用Unused占位。想把默认行为并入期望用DoDefault()但牢记其不可嵌入组合动作、不可用于ON_CALL。想串联/改写入参先副作用后返回用DoAll前导动作须为 void忽略结果用IgnoreResult重排/挑选/丢弃参数用WithArg/WithArgs/WithoutArgs。内置不够用小而专用用ACTION/ACTION_P/ACTION_Pk只能在命名空间作用域定义高复用、要求精细类型控制则实现ActionInterface或PolymorphicAction。每个动作的签名、所有权语义与平台/编译器限制均可在本仓库对应源码中逐一核对动作声明集中在 googlemock/include/gmock/gmock-actions.h 与 googlemock/include/gmock/gmock-more-actions.h行为契约由 googlemock/test/gmock-actions_test.cc 与 googlemock/test/gmock-more-actions_test.cc 中的数百个测试守护。若需进一步了解动作与EXPECT_CALL/ON_CALL的配合范式可继续阅读 gmock_for_dummies、gmock_cook_book 与 gmock_cheat_sheet。【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考