
如何在多线程测试场景中安全使用 GoogleMock mock 对象【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest当被测代码本身是多线程的——例如事件在后台线程上派发、多个线程并发调用同一个依赖——单元测试里的 GoogleMock mock 对象就会被多个线程同时访问。googletest 仓库的 gMock Cookbook 在 Using gMock and Threads 一节明确给出了一套规则只要区分清楚 mock 使用流程中哪些步骤必须独占访问、哪些步骤可以由 gMock 自己加锁保护mock 和线程就能安全共存违反这些规则比如在另一个线程正在调用 mock 方法时设置期望会得到未定义行为undefined behavior。本文基于项目文档给出完整的操作路径和验证方式。先对照 mock 使用流程判断每个步骤能开几个线程gMock Cookbook 回顾了一个 mock 的标准使用步骤创建 mock 对象foo用ON_CALL()和EXPECT_CALL()设置默认动作和期望被测代码调用foo的方法可选地验证并重置 mock由你自己或代码销毁 mock析构函数会自动验证剩余期望。文档要求把测试代码区别于被测代码放在一个线程里执行并给出每一步的线程约束步骤 1创建 mock不需要任何锁步骤 2设置默认动作和期望与步骤 5销毁必须保证没有其他线程正在访问该 mock步骤 3 和 4 可以在一个或多个线程里执行——gMock 负责加锁测试代码不需要额外处理除非测试逻辑本身要求。因此操作路径是先在主线程创建 mock 并设完全部期望再把 mock 交给被测代码让多个线程并发调用最后在没有其他线程访问时做验证或让它析构。文档明确警告如果在另一个线程调用 mock 方法的同时设置期望行为未定义Thats not fun, so dont do it。另外注意gmock_for_dummies 中的 Important note 还强调gMock 要求期望必须在 mock 函数被调用之前设置不能把EXPECT_CALL()与对 mock 方法的调用交错执行也不能在把 mock 交给被测 API 之后再设置期望否则行为未定义。理解 action 在多个线程中的执行方式理解这一点才能正确设计并发测试gMock 保证 mock 函数的 action 在调用该 mock 函数的同一个线程中执行。文档给出的例子EXPECT_CALL(mock, Foo(1)) .WillOnce(action1); EXPECT_CALL(mock, Foo(2)) .WillOnce(action2);如果Foo(1)在 thread 1 被调用、Foo(2)在 thread 2 被调用gMock 会在 thread 1 执行action1、在 thread 2 执行action2。gMock不会对不同线程中执行的 action 强加顺序强加顺序可能造成死锁因为各 action 之间可能需要协作。所以上例中action1和action2的执行可能交错如果这会造成问题文档建议的正确做法是在action1和action2里添加适当的同步逻辑让测试自身线程安全而不是指望 gMock 排序。把异步调用变成确定性的等待如果多线程问题来自异步行为例如被测类把事件放到后台线程派发文档不建议插入sleep()碰运气而是用 gMock action 加Notification对象强制异步测试同步化。gMock Cookbook 中 Testing Asynchronous Behavior 给出的示例依赖示例中使用的absl::Notificationclass MockEventDispatcher : public EventDispatcher { MOCK_METHOD(bool, DispatchEvent, (int32), (override)); }; TEST(EventQueueTest, EnqueueEventTest) { MockEventDispatcher mock_event_dispatcher; EventQueue event_queue(mock_event_dispatcher); const int32 kEventId 321; absl::Notification done; EXPECT_CALL(mock_event_dispatcher, DispatchEvent(kEventId)) .WillOnce([done] { done.Notify(); }); event_queue.EnqueueEvent(kEventId); done.WaitForNotification(); }做法是照常设置期望再用WillOnce追加一个动作去done.Notify()主线程随后调用WaitForNotification()等待后台线程完成这次 mock 调用测试结束时可以安全退出。文档同时指出这个模式的缺点如果期望没有被满足测试会一直等下去最终靠超时失败但调试起来更慢。缓解方式是把WaitForNotification()换成WaitForNotificationWithTimeout(ms)。在多线程测试中验证期望mock 析构时会自动验证所有期望是否满足不满足会生成 GoogleTest 失败。例如期望未被满足时的失败信息形如文档示例path/to/my_test.cc:119: Failure Actual function call count doesnt match this expectation: Actually: never called; Expected: called at least once. Stack trace: ...但如果在多线程场景中 mock 由被测代码持有、无法保证最终被销毁或被测代码有 bug 忘了 delete自动验证就不可靠。这时按 gMock Cookbook Forcing a Verification 和 gMock Cheat Sheet 的说明在主线程显式强制验证TEST(MyServerTest, ProcessesRequest) { using ::testing::Mock; MockFoo* const foo new MockFoo; EXPECT_CALL(*foo, ...)...; // ... other expectations ... // server now owns foo. MyServer server(foo); server.ProcessRequest(...); // In case that servers destructor will forget to delete foo, // this will verify the expectations anyway. Mock::VerifyAndClearExpectations(foo); } // server is destroyed when it goes out of scope here.两个函数都返回bool仅当验证成功时为trueMock::VerifyAndClearExpectations(mock_obj)只验证并移除期望Mock::VerifyAndClear(mock_obj)还额外移除ON_CALL()设置的默认动作。Cheat Sheet 建议把调用包进ASSERT_TRUE()这样验证失败时不必继续执行后续逻辑。如果 mock 确实可能被泄漏且不需要验证Cheat Sheet 还给出了Mock::AllowLeak(mock_obj)。这里有一条必须遵守的限制验证并清空之后不要再设置新的期望。在已经运行过 mock 的代码之后再设置期望属于未定义行为gMock Cookbook 和 Cheat Sheet 都对此有明确说明。限制与排查手段DefaultValueT是全局资源会影响程序中所有存活的 mock 对象。文档明确不要从多个线程修改它也不要还有 mock 在活动in action时去动它。调试期望匹配问题时用--gmock_verboseinfo运行测试gMock 会打印每个ON_CALL/EXPECT_CALL的设置日志、每次 mock 调用的匹配情况、参数值和返回值的栈跟踪文档示例输出中可以看到EXPECT_CALL(mock, F(_, _)) invoked及Mock function call matches ...这类记录。测试代码内也可以用::testing::FLAGS_gmock_verbose error;调整该标志如果栈帧太多可以用--gtest_stack_trace_depthmax_depth限制数量。gMock FAQ 还解释了一个多线程相关现象如果测试崩溃且进程里有很多深栈的线程失败信号处理器会记录大量信息栈跟踪、地址映射等ScopedMockLog拦截到这些不匹配期望的日志后会为每一条打印错误。文档说明这通常不是 gMock 的 bug可以通过放宽期望例如对非本测试产生的日志行Times(AnyNumber())让测试更健壮。操作路径小结按文档规则执行主线程创建 mock 并设完期望此时无其他线程访问→ 被测代码多线程调用 mock 方法action 在各自调用线程执行、必要时在 action 内自行同步 → 所有线程停止访问后靠析构自动验证或对堆上 mock 用Mock::VerifyAndClearExpectations/Mock::VerifyAndClear显式验证并用ASSERT_TRUE检查返回值。异步派发场景用Notification加超时等待替代sleep()。只要每一步落在文档允许的线程约束内验证失败信息Actual function call count doesnt match... 这类输出就是判断期望是否被正确满足的依据。【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考