You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 18:15:36