如何用 absl::Notification 让 GoogleMock 异步测试确定性地等待后台线程完成
【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest
当你用 GoogleMock(googletest 仓库中的 gMock)测试一个在后台线程执行工作的类时,测试主线程很难知道后台任务何时完成。典型的例子是 gMock Cookbook 中的场景:EventQueue类把所有事件交给一个EventDispatcher接口去分发,而真实实现是在一个后台线程上触发这些事件,导致测试时序难以控制。直接插sleep()语句只是"碰运气",会让测试行为变得不确定。解决方式是:给 mock 的期望加一个调用absl::Notification的 action,让主线程用WaitForNotification()阻塞等待,把异步测试变成同步的、确定性的。
这个模式为什么有效
使用 gMock 与线程 一节给出了两条直接支撑该模式的关键事实:
- gMock 保证 mock 函数的 action 在调用该 mock 函数的那个线程中执行。因此当后台线程调用
DispatchEvent()时,挂在期望上的 action(也就是done.Notify())会在后台线程里运行。 - 测试代码本身应当只在一个线程中执行;在设置期望(
ON_CALL/EXPECT_CALL)和销毁 mock 时,确保没有其他线程正在访问该 mock,否则是未定义行为。gMock 自己负责 mock 调用的锁,不需要你额外加锁。
基于这两点,"后台线程 action 里Notify(),主线程WaitForNotification()" 就构成了一条确定性的等待路径:主线程只有在后台线程真正执行了那个期望时才会被唤醒。
操作步骤
以下代码取自 gMock Cookbook 的 Testing Asynchronous Behavior 小节。示例中的EventQueue对应你的被测类,EventDispatcher对应你为 mock 出来的分发接口,kEventId是对应的入参值;换成你自己的类型后结构不变。前提是测试工程能够使用 Abseil 库(示例用到了absl::Notification)。
第一步,定义 mock 类,用MOCK_METHOD声明要 mock 的方法:
class MockEventDispatcher : public EventDispatcher { MOCK_METHOD(bool, DispatchEvent, (int32), (override)); };第二步,在测试里先建 mock 和被测对象,然后创建absl::Notification done,并对目标期望挂一个"调用一次就通知"的 lambda action:
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(); }各步骤的作用:
absl::Notification done;:声明同步对象,供两个线程共享。EXPECT_CALL(...).WillOnce([&done] { done.Notify(); }):按正常方式设置 gMock 期望,并在期望命中时额外执行一次done.Notify()。这一步必须在触发异步调用之前完成(即测试线程单线程设置期望,后台线程尚未调用 mock)。event_queue.EnqueueEvent(kEventId);:触发被测代码。被测代码随后会在后台线程调用DispatchEvent(kEventId),命中上面的期望并执行done.Notify()。done.WaitForNotification();:主线程在此阻塞,直到Notify()被调用后才返回。
如何判断测试写对了
成功条件是文档明确描述的:调用WaitForNotification()后,主线程只有在后台线程执行完那个期望(即异步调用完成)时才会继续,此时"测试套件完成,可以安全退出"。不再需要sleep(),测试结果是确定性的。
文档同时指出一个必须知道的缺点:如果期望没有被满足(例如被测代码根本没调用DispatchEvent(kEventId)),WaitForNotification()会一直等待,测试会一直运行下去,最终虽然会因超时失败,但耗时更长且更难调试。文档给出的缓解办法是把WaitForNotification()换成带超时的版本:
// 期望未被满足时,等待超时后返回,而不是永久挂起 done.WaitForNotificationWithTimeout(ms);其中ms是等待上限,由你根据后台任务的合理耗时给出。
限制与注意事项
- 该模式要求 mock 的期望在触发异步调用之前设置完毕,且测试代码只在主线程执行;如果在另一个线程调用 mock 方法的同时设置期望,行为未定义。
- gMock 不会对不同线程中执行的 action 排序,多个跨线程 action 可能交错执行;如果需要有序,要在 action 内自行加入同步逻辑。
- 示例只演示了单个期望的一次性通知。多个异步事件可以各建一个
Notification,或改用带超时的等待,文档未给出更多变体。
进一步阅读可参考 gMock Cookbook 的 "Testing Asynchronous Behavior" 与 "Using gMock and Threads" 两节。
【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考