
GoogleTest Actions 参考指南yaml-cpp 测试中的 Mock 行为设定全解【免费下载链接】yaml-cppA YAML parser and emitter in C项目地址: https://gitcode.com/GitHub_Trending/ya/yaml-cpp导读Actions动作是 GoogleTest 中定义mock 函数被调用时应做什么的核心机制它决定了 mock 对象在测试中如何返回值、产生副作用、调用真实函数或执行自定义逻辑。本文以 GoogleTest 1.16.0 官方 Actions Reference位于 test/googletest-1.16.0/docs/reference/actions.md为骨架结合 yaml-cpp 仓库内的真实测试代码test/mock_event_handler.h、test/integration/handler_test.h与 gmock-actions.h 源码实现系统讲解全部内置 Action 的分类、语义、组合方式与自定义手法。读完本文你将能够在 yaml-cpp 这类 C 项目中熟练编写EXPECT_CALL/ON_CALL驱动的事件处理 mock并理解每个 Action 背后的实现原理与适用场景。一、理解 Actionsmock 行为的最小执行单元在 GoogleTest 中Action指mock 函数被调用后执行的一段行为。所有内置 Action 均定义于::testing命名空间。它通常配合WillOnce()、WillRepeatedly()或ON_CALL()使用EXPECT_CALL(mock, Foo(_, _, _)).WillOnce(Return(42));yaml-cpp 的测试正是这一机制的典型实践。仓库通过 test/mock_event_handler.h 用MOCK_METHOD*宏定义了MockEventHandlermock 掉EventHandler的全部事件回调class MockEventHandler : public EventHandler { public: MOCK_METHOD1(OnDocumentStart, void(const Mark)); MOCK_METHOD0(OnDocumentEnd, void()); MOCK_METHOD2(OnNull, void(const Mark, anchor_t)); MOCK_METHOD4(OnScalar, void(const Mark, const std::string, anchor_t, const std::string)); MOCK_METHOD4(OnSequenceStart, void(const Mark, const std::string, anchor_t, EmitterStyle::value)); MOCK_METHOD0(OnSequenceEnd, void()); MOCK_METHOD4(OnMapStart, void(const Mark, const std::string, anchor_t, EmitterStyle::value)); MOCK_METHOD0(OnMapEnd, void()); MOCK_METHOD2(OnAnchor, void(const Mark, const std::string)); };由于这些回调返回类型均为void测试中使用最多的就是Return()见下文而EXPECT_CALL主要用来校验事件调用顺序与参数。其测试基类 test/integration/handler_test.h 同时维护了StrictMockhandler与NiceMocknice_handler两个实例分别用于严格断言与忽略无关调用的场景。二、返回值类 Actions下表完整列出返回值类内置 Action均来自官方 Reference 原文Action说明Return()从一个返回void的 mock 函数中返回。Return(value)返回value。若value的类型与 mock 函数返回类型不同则在设定期望时而非 action 执行时完成类型转换。ReturnArgN()返回第N0 起个参数。ReturnNewT(a1, ..., ak)返回new T(a1, ..., ak)每次调用创建不同对象。ReturnNull()返回空指针。ReturnPointee(ptr)返回ptr所指向的值。ReturnRef(variable)返回对variable的引用。ReturnRefOfCopy(value)返回value副本的引用副本生命周期与 action 一致。ReturnRoundRobin({a1, ..., ak})每次调用依次返回下一个ai到达列表末尾后从头循环。2.1 Return() 与 Return(value) 的实现细节在源码 gmock-actions.h 中ReturnAction的实现揭示了几个关键点转换时点Return(value)在设置期望时就急切eagerly将R转换为 mock 函数的返回类型U而不是每次调用时惰性转换。源码注释中专门用std::string传给返回std::string_view的场景说明急切转换并保留原始输入值State::input_value是为了避免引用类返回类型悬垂。可移动性ReturnAction同时提供operator OnceActionU(Args...)() 允许移动语义与operator ActionU(Args...)() const拷贝语义两个转换路径编译器依据使用场景自动选择保证移动构造/拷贝构造均可用。类型约束要求R可隐式转换std::is_convertible到U且U可移动构造/拷贝构造这保证了类型安全性。2.2 空指针与循环返回值ReturnNull()适用于返回指针的 mockReturnRoundRobin({a1, ..., ak})在 gmock-actions.h 中以共享状态实现——内部用std::shared_ptrState保存值列表与游标iNext()取当前值并递增游标到末尾归零循环且构造时校验列表非空GTEST_CHECK_(!values.empty())非常适合模拟依次喂入一组数据、循环复用的场景例如按脚本返回 YAML 节点的不同取值。三、副作用类 ActionsAction说明Assign(variable, value)将value赋值给变量。DeleteArgN()delete第N0 起个参数该参数必须是指针。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个参数指向的数组该参数可为指针或迭代器。action 不接管源区间元素的所有权。SetErrnoAndReturn(error, value)设置errno为error并返回value。Throw(exception)抛出给定异常任何可拷贝值均可自 v1.1.0 可用。Assign与SetErrnoAndReturn在源码 gmock-actions.h 中有直白实现AssignAction持有T1* ptr_与T2 value_在Perform中执行*ptr_ value_SetErrnoAndReturnAction则在Perform中先写errno再返回结果。注意后者受平台条件编译保护#ifndef GTEST_OS_WINDOWS_MOBILE在 Windows Mobile 以外平台可用适合模拟read、open等 C 库函数在错误路径上的行为——例如测试 YAML 流解析的 I/O 失败场景。四、以函数 / 仿函数 / Lambda 作为 Action本节中可调用对象callable指自由函数、std::function、仿函数或 lambdaAction说明f直接传入可调用对象f以 mock 函数收到的参数调用它。Invoke(f)以 mock 函数的参数调用ff可为全局/静态函数或仿函数。Invoke(object_pointer, class::method)以 mock 函数的参数调用对象的成员方法。InvokeWithoutArgs(f)调用不接收参数的f全局/静态函数或仿函数。InvokeWithoutArgs(object_pointer, class::method)调用对象的无参成员方法。InvokeArgumentN(arg1, ..., argk)调用 mock 函数的第N个参数必须是函数或仿函数并传入k个实参。被调用函数的返回值即 action 的返回值。4.1 用 Unused 忽略多余参数定义配合Invoke*()使用的可调用对象时未用到的参数可声明为Unusedusing ::testing::Invoke; double Distance(Unused, double x, double y) { return sqrt(x*x y*y); } ... EXPECT_CALL(mock, Foo(Hi, _, _)).WillOnce(Invoke(Distance));4.2 回调所有权约束Invoke(callback)与InvokeWithoutArgs(callback)会接管 callback 的所有权callback 必须是永久permanent对象且类型必须是基类回调类型而非派生类型BlockingClosure* done new BlockingClosure; ... Invoke(done) ...; // 编译失败派生类型 Closure* done2 new BlockingClosure; ... Invoke(done2) ...; // 正确基类类型4.3 InvokeArgument 的传参方式在InvokeArgumentN(...)中若某参数需要按引用传递用std::ref()包裹using ::testing::InvokeArgument; ... InvokeArgument2(5, string(Hi), std::ref(foo))上述代码调用 mock 函数的第 2 个参数该参数本身是一个函数/仿函数向它按值传入5和string(Hi)按引用传入foo。五、默认动作DoDefault()Action说明DoDefault()执行默认动作由ON_CALL()设定或使用内置默认动作。其实现见 gmock-actions.hDoDefaultAction通过模板类型转换运算符operator ActionF() const返回一个空ActionF让运行时决策机制接管。注意事项由于技术原因DoDefault()不能在复合 action 内部使用——若在DoAll(...)等复合动作中使用DoDefault()将导致运行时错误。典型用法是将默认行为与特定调用的定制分层ON_CALL(mock, GetValue(_)).WillByDefault(Return(-1)); // 默认返回 -1 EXPECT_CALL(mock, GetValue(5)).WillOnce(DoDefault()); // 对特定参数仍走默认yaml-cpp 中对应的分层思路体现在 test/integration/handler_test.h用NiceMockMockEventHandler nice_handler承载不关心的调用走默认宽松行为用StrictMock对关键事件做精确断言二者并存互不干扰。六、复合 ActionsAction说明DoAll(a1, a2, ..., an)每次调用依次执行a1~an返回an的结果前n-1个子 action 必须返回void且只能收到参数的只读视图。IgnoreResult(a)执行 actiona并忽略其结果a不能返回void。WithArgN(a)将 mock 函数的第N0 起个参数传给 actiona并执行。WithArgsN1, N2, ..., Nk(a)将选中的0 起参数传给 actiona并执行。WithoutArgs(a)不传任何参数地执行 actiona。DoAll的源码结构见 gmock-actions.hDoAllAction是模板递归的复合动作——以最后一个动作为FinalAction其余动作按顺序串联逐个执行副作用再返回最终结果这正是先记账、后返回模式的基础。典型组合示例——先保存实参再返回值EXPECT_CALL(mock, SetValue(_)) .WillOnce(DoAll(SaveArg0(saved), Return(true)));七、定义自定义 Actions宏说明ACTION(Sum) { return arg0 arg1; }定义 actionSum()返回 mock 函数第 0、1 个参数之和。ACTION_P(Plus, n) { return arg0 n; }定义带参 actionPlus(n)返回第 0 个参数与n之和。ACTION_Pk(Foo, p1, ..., pk) { statements; }定义带k个参数的 actionFoo(p1, ..., pk)执行给定语句。关键约束ACTION*宏不能用在函数或类内部必须位于命名空间作用域原因在于其展开会生成模板而模板无法在局部类中声明作为替代局部作用域内可以定义任意局部仿函数如 lambda充当 action。7.1 ACTION 宏内部的可用符号从 gmock-actions.h 头部注释可见宏体内可引用以下符号arg0、arg1、…、argKmock 函数的第 K 个0 起实参argK_type对应实参的类型args/args_type完整实参元组及其类型function_type/return_typemock 函数类型与返回类型param_typeACTION_P*宏中参数的类型如n_type。示例ACTION(IncrementArg1) { arg1_type temp arg1; return (*temp); } // 用法...WillOnce(IncrementArg1());无需手写参数类型代码依然类型安全——若*arg1不支持或结果与返回类型不兼容编译器会直接报错。7.2 参数化与重载ACTION_P支持 1 个参数ACTION_P2~ACTION_P10支持多参数从类型角度ACTION_Pk(Foo, p1, ..., pk) { ... }等价于模板函数FooActionPkp1_type, ..., pk_type Foo(p1_type p1, ..., pk_type pk)因此既可让编译器自动推导类型也可显式指定如Foolong, bool(5, false)可按参数个数重载ACTION_P(Plus, a)与ACTION_P2(Plus, a, b)可并存。7.3 面向重用的进阶方案源码注释明确建议若自定义 action 被大量使用应优先考虑实现ActionInterface或使用MakePolymorphicAction()见 gmock-actions.h。虽然这需要更多代码但能对 mock 函数的实参类型与 action 参数类型获得更细粒度的控制从而得到更好的编译错误信息且支持按参数类型而不只是按参数个数重载。这正是内置ReturnRoundRobinAction等实现所采用的模式——ReturnRoundRobinAction 通过模板T operator()(Args...)实现了对任意返回类型的多态支持。八、在 yaml-cpp 项目中落地完整实战示例结合本仓库一个综合运用上述 Action 的事件驱动测试如下。首先复用 MockEventHandler 定义然后在测试中编排期望#include mock_event_handler.h #include yaml-cpp/yaml.h #include gmock/gmock.h #include gtest/gtest.h using ::testing::_; using ::testing::InSequence; using ::testing::NiceMock; using ::testing::Return; using ::testing::StrictMock; TEST(YamlEventHandlerTest, SequenceScalarFlow) { InSequence seq; // 严格按序断言事件 StrictMockMockEventHandler handler; EXPECT_CALL(handler, OnDocumentStart(_)).WillOnce(Return()); // void 返回 EXPECT_CALL(handler, OnSequenceStart(_, ?, 0, EmitterStyle::Block)) .WillOnce(Return()); EXPECT_CALL(handler, OnScalar(_, foo, 0, plain)).WillOnce(Return()); EXPECT_CALL(handler, OnSequenceEnd()).WillOnce(Return()); EXPECT_CALL(handler, OnDocumentEnd()).WillOnce(Return()); // 使用真实解析器驱动 mock std::stringstream stream(- foo\n); YAML::Parser parser(stream); while (parser.HandleNextDocument(handler)) { } }要点拆解Return()是 void 回调的标准动作。yaml-cpp 的EventHandler回调全部返回void因此 test/integration/encoding_test.cpp、test/integration/gen_emitter_test.cpp 中大量使用EXPECT_CALL(handler, OnXxx(...))并配合InSequence校验事件顺序——当 action 缺省时void 类型 mock 会执行默认的无操作行为。InSequence与 mock 组合StrictMock对未声明的调用直接报错适合断言完整事件流NiceMock容忍未声明调用适合只关注部分事件的场景。二者都在 test/integration/handler_test.h 中同时维护。参数匹配器_OnScalar(_, foo, 0, plain)中的_匹配任意Mark配合精确标量值校验是 Actions 与 Matchers 协同的典型用法。若需模拟解析中抛出异常或设置系统错误可借助Throw与SetErrnoAndReturn若要依次喂入多个预期标量再循环则ReturnRoundRobin({a, b, c})正合适。九、总结与选型速查返回值优先Return(value)/ReturnNull()/ReturnRef(...)需要按参数返回用ReturnArgN()需要循环序列用ReturnRoundRobin(...)。副作用写回调用方变量用Assign/SetArgReferee/SetArgPointee读取实参用SaveArg/SaveArgPointee模拟系统调用用SetErrnoAndReturn模拟异常路径用Throw。真实行为委托给自由函数 / lambda / 成员方法用Invoke*系列。默认与复合区分默认行为用ON_CALLDoDefault()串联多个副作用再返回结果用DoAll裁剪参数再执行用WithArg/WithArgs/WithoutArgs忽略返回值用IgnoreResult。自定义简单场景用ACTION/ACTION_P/ACTION_Pk限命名空间作用域高频复用或需按类型重载时实现ActionInterface或MakePolymorphicAction()。本文所有内置 Action 的语义均以官方 Referencetest/googletest-1.16.0/docs/reference/actions.md为准实现细节可直接查阅 gmock-actions.h仓库内的 MockEventHandler 与各 integration 测试 则是文档 源码落地到真实 C 项目的最佳范本。【免费下载链接】yaml-cppA YAML parser and emitter in C项目地址: https://gitcode.com/GitHub_Trending/ya/yaml-cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考