如何用GoogleMock模拟方法验证定时器功能(无延迟单元测试)
用GoogleMock无延迟验证定时器回调间隔的方案
要解决这个问题,核心思路是完全模拟定时器的行为,跳过真实的时间等待——毕竟单元测试里硬等时间太影响效率了。我们需要通过依赖注入或者全局函数替换,把定时器的控制权拿到测试代码里,手动触发回调并记录时间戳来验证间隔。
方案一:通过依赖注入抽象定时器(推荐,符合开闭原则)
如果可以修改TimerWrapper的代码,最好的方式是把底层的定时器逻辑抽象成接口,让TimerWrapper依赖接口而非具体实现,这样测试时就能注入一个mock定时器来完全控制行为。
步骤1:抽象定时器接口
先定义一个定时器接口,把CreateTimer的行为封装进去:
#include <functional> class ITimer { public: virtual ~ITimer() = default; // 原CreateTimer的签名 virtual void CreateTimer(int freq, int interval, std::function<void()> callback) = 0; }; // 真实环境的定时器实现(包装原有的CreateTimer) class RealTimer : public ITimer { public: void CreateTimer(int freq, int interval, std::function<void()> callback) override { ::CreateTimer(freq, interval, callback); // 调用原全局函数 } };
步骤2:修改TimerWrapper依赖接口
让TimerWrapper接收ITimer的实例,而不是直接调用全局CreateTimer:
void TimerWrapper(ITimer& timer, int freq, int interval, std::function<void()> callback) { timer.CreateTimer(freq, interval, callback); }
步骤3:实现MockTimer并编写测试
用GoogleMock实现mock定时器,并且添加辅助方法来模拟按间隔触发回调,同时记录时间戳:
#include <gmock/gmock.h> #include <vector> class MockTimer : public ITimer { public: MOCK_METHOD(void, CreateTimer, (int freq, int interval, std::function<void()> callback), (override)); // 模拟按指定间隔触发回调freq次,记录每次的模拟时间戳 void SimulateTimerTriggers(int freq, int interval_ms, std::vector<int>& out_timestamps) { out_timestamps.clear(); int current_sim_time = 0; for (int i = 0; i < freq; ++i) { if (callback_) { callback_(); // 触发回调 out_timestamps.push_back(current_sim_time); current_sim_time += interval_ms; // 模拟时间流逝 } } } // 用来保存从CreateTimer中捕获的回调函数 std::function<void()> callback_; }; TEST_F(TestTimer, VerifyCallbackIntervalAndCount) { const int kExpectedFreq = 5; const int kExpectedIntervalMs = 1000; // 1秒间隔 MockTimer mock_timer; std::vector<int> actual_timestamps; int callback_invocation_count = 0; // 定义测试用的回调:记录调用次数 auto test_callback = [&]() { callback_invocation_count++; }; // 设置GoogleMock期望:捕获TimerWrapper传递的回调函数 EXPECT_CALL(mock_timer, CreateTimer(kExpectedFreq, kExpectedIntervalMs, _)) .WillOnce(testing::SaveArg<2>(&mock_timer.callback_)); // 调用待测试的TimerWrapper TimerWrapper(mock_timer, kExpectedFreq, kExpectedIntervalMs, test_callback); // 模拟定时器按间隔触发所有回调 mock_timer.SimulateTimerTriggers(kExpectedFreq, kExpectedIntervalMs, actual_timestamps); // 验证回调调用次数是否符合预期 EXPECT_EQ(callback_invocation_count, kExpectedFreq); // 验证每次回调的时间间隔是否正确 for (int i = 1; i < actual_timestamps.size(); ++i) { EXPECT_EQ(actual_timestamps[i] - actual_timestamps[i-1], kExpectedIntervalMs); } }
方案二:全局函数替换(适用于无法修改TimerWrapper的遗留代码)
如果不能修改TimerWrapper的代码,我们可以替换全局的CreateTimer函数,在测试中保存回调和参数,然后手动触发回调并验证间隔:
步骤1:替换全局CreateTimer
在测试文件中重新定义CreateTimer(注意链接顺序,确保测试中的版本被优先调用):
#include <functional> #include <vector> // 全局变量保存从TimerWrapper中传递的参数和回调 static std::function<void()> g_saved_callback; static int g_saved_freq; static int g_saved_interval; // 替换原有的CreateTimer函数 void CreateTimer(int freq, int interval, std::function<void()> callback) { g_saved_callback = callback; g_saved_freq = freq; g_saved_interval = interval; // 这里不做真实的定时器逻辑,只保存参数 }
步骤2:编写测试用例
#include <gmock/gmock.h> TEST_F(TestTimer, VerifyIntervalWithoutDelay_LegacyCode) { const int kExpectedFreq = 5; const int kExpectedIntervalMs = 1000; int callback_count = 0; std::vector<int> timestamps; int current_sim_time = 0; // 测试回调:记录调用次数和模拟时间戳 auto test_callback = [&]() { callback_count++; timestamps.push_back(current_sim_time); current_sim_time += kExpectedIntervalMs; }; // 调用待测试的TimerWrapper TimerWrapper(kExpectedFreq, kExpectedIntervalMs, test_callback); // 手动触发回调kExpectedFreq次 for (int i = 0; i < kExpectedFreq; ++i) { g_saved_callback(); } // 验证调用次数 EXPECT_EQ(callback_count, kExpectedFreq); // 验证时间间隔 for (int i = 1; i < timestamps.size(); ++i) { EXPECT_EQ(timestamps[i] - timestamps[i-1], kExpectedIntervalMs); } }
关键要点说明
- 避免真实时间等待:两种方案都是通过手动触发回调+模拟时间戳的方式,完全跳过了真实的定时器延迟,测试可以瞬间完成。
- 参数验证:除了间隔和次数,你还可以添加
EXPECT_CALL来验证TimerWrapper是否正确传递了freq和interval参数给定时器。 - 线程安全:如果测试是多线程的,全局函数替换方案需要注意全局变量的线程隔离(比如用线程局部存储),而依赖注入方案天然更安全。
内容的提问来源于stack exchange,提问作者xyf
相关产品推荐
相关产品推荐

