如何用Gtest Mock模拟resize与Getsize的指定时序竞态
结论
完全可以通过gtest配套的gMock能力实现这个精准时序的竞态场景模拟,而且不需要靠不稳定的随机休眠来碰调度时机,能做到100%复现你要的缓存不一致问题。
实现思路
你不需要在代码里硬编码延迟,核心做法是通过mock插桩,卡在resizeint执行完成、writenewsizetocache还未执行的精确代码点位,主动触发Getsize调用,完全控制执行时序。
第一步:做最小程度的依赖解耦
因为gMock默认对虚接口做动态mock,你需要先把resizeint、writenewsizetocache、readsizefromlocalcache这几个底层操作抽成可注入的接口,不需要改动原有业务逻辑,只是把硬编码的内部依赖改成外部传入即可:
// 抽离底层操作的抽象接口 class SharedObjectBase { public: virtual ~SharedObjectBase() = default; virtual void resizeint(int newsize) = 0; virtual void writenewsizetocache() = 0; virtual int readsizefromlocalcache() = 0; }; // 原有业务逻辑几乎不变,只是依赖抽象接口 class SizeHandler { private: SharedObjectBase* obj_; public: explicit SizeHandler(SharedObjectBase* obj) : obj_(obj) {} void Resize(int newsize) { obj_->resizeint(newsize); // 目标插桩点就在这两行中间 obj_->writenewsizetocache(); } int Getsize() { return obj_->readsizefromlocalcache(); } };
第二步:编写Mock类
用gMock的宏生成对应接口的mock实现:
class MockSharedObject : public SharedObjectBase { public: MOCK_METHOD(void, resizeint, (int newsize), (override)); MOCK_METHOD(void, writenewsizetocache, (), (override)); MOCK_METHOD(int, readsizefromlocalcache, (), (override)); };
第三步:编写测试用例,通过mock动作控制时序
不要用sleep之类的延迟等待,直接用gMock的Invoke动作,在resizeint被调用的逻辑末尾,主动执行Getsize,这个时机完全卡在你要的两个操作中间,没有任何调度偏差:
TEST(CacheConsistencyTest, GetOldSizeWhenResizeNotFinishCacheUpdate) { MockSharedObject mock_obj; SizeHandler handler(&mock_obj); const int old_size = 100; const int new_size = 300; // 设定预期:读缓存时因为还没更新,返回旧尺寸 EXPECT_CALL(mock_obj, readsizefromlocalcache()) .WillOnce(testing::Return(old_size)); // 设定预期:缓存写入操作会在resizeint之后执行 EXPECT_CALL(mock_obj, writenewsizetocache()) .Times(1); // 核心插桩逻辑:resizeint执行完立刻调用Getsize EXPECT_CALL(mock_obj, resizeint(new_size)) .WillOnce(testing::Invoke([&](int) { // 这里就是目标精确点位:实际尺寸已经修改,缓存还未写入 int ret = handler.Getsize(); // 验证此时读到的确实是旧缓存值,复现不一致问题 EXPECT_EQ(ret, old_size); })); // 执行Resize操作,时序完全按照设定执行 handler.Resize(new_size); }
方案说明
- 没有任何不稳定的延迟等待,完全靠代码执行流控制时机,每次运行都能稳定复现场景
- 不需要修改生产代码的业务逻辑,只做了依赖解耦,本身也符合代码设计的最佳实践
- 如果你的底层函数是全局函数/静态函数,没法抽虚接口,也可以用gMock的编译期/链接期打桩能力实现同样的插桩效果,只是可维护性比接口注入稍差。
不要靠随机休眠模拟竞态:操作系统的线程调度是不可控的,哪怕你加固定时长的sleep,也可能出现调度偏差导致测试偶发失败,用mock主动在指定执行点插入逻辑才是可靠的方案。
内容的提问来源于stack exchange,提问作者vidhya gopalan
相关产品推荐
相关产品推荐

