测试驱动开发:如何将时间逻辑从代码中解耦
依赖时间触发行为的类的确定性测试方法及时间逻辑外部化实现
一、确定性测试的通用方法
- 依赖注入:将时间相关的依赖(如定时器、时钟)从类内部构造改为外部传入,测试时可替换为Mock对象,完全控制时间相关行为。
- 抽象时钟/定时器接口:定义抽象接口封装时间操作,业务类依赖接口而非具体实现,测试时用模拟实现手动控制时间流逝。
- 时间伪造:在测试环境中替换系统时间的获取逻辑,让时间可以按测试需求推进,不受真实系统时间或负载影响。
- 状态驱动验证:不等待真实时间,通过监听类的状态变化、接口调用记录、回调触发次数等方式,直接验证业务逻辑的正确性。
二、将时间逻辑从PassiveBuzzer类外部化的具体实现
1. 定义定时器抽象接口
先封装定时器的核心操作成抽象接口,让现有timerfd包装类实现该接口:
class TimerInterface { public: virtual ~TimerInterface() = default; virtual void start(std::chrono::nanoseconds period) = 0; virtual void stop() = 0; // 按需添加修改周期、获取运行状态等方法 }; // 原有timerfd包装类适配接口 class TimerFD : public TimerInterface { public: void start(std::chrono::nanoseconds period) override { // 原有timerfd启动逻辑 } void stop() override { // 原有timerfd停止逻辑 } };
2. 修改PassiveBuzzer构造函数
将定时器依赖改为外部传入,取消内部构造逻辑:
class PassiveBuzzer { private: std::unique_ptr<TimerInterface> timer_; PWM::Line& pwm_line_; unsigned int on_frequency_; public: PassiveBuzzer(std::unique_ptr<TimerInterface> timer, PWM::Line& pwm_line, unsigned int on_frequency) : timer_(std::move(timer)), pwm_line_(pwm_line), on_frequency_(on_frequency) {} // 原有业务方法示例 void setDutyCycle(float duty) { // 业务逻辑中按需调用timer_->start(...)等接口 } };
3. 实现测试用Mock定时器
在单元测试中编写Mock定时器,记录调用行为并支持手动触发回调:
class MockTimer : public TimerInterface { public: int start_call_count = 0; std::chrono::nanoseconds last_set_period; bool is_running = false; std::function<void()> timer_callback; // 存储定时器到期后的回调 void start(std::chrono::nanoseconds period) override { start_call_count++; last_set_period = period; is_running = true; } void stop() override { is_running = false; } // 手动触发定时器到期逻辑 void trigger_expiry() { if (timer_callback) { timer_callback(); } } };
4. 执行确定性测试
通过Mock定时器验证PassiveBuzzer的行为:
TEST(PassiveBuzzerTest, CorrectlyConfiguresTimer) { MockTimer mock_timer; MockPWMLine mock_pwm; // 你已有的Mock PWM线路实现 PassiveBuzzer buzzer(std::make_unique<MockTimer>(mock_timer), mock_pwm, 440); buzzer.setDutyCycle(0.5f); // 验证定时器启动参数是否正确 EXPECT_EQ(mock_timer.start_call_count, 1); EXPECT_EQ(mock_timer.last_set_period, std::chrono::nanoseconds(1000000000 / 440)); EXPECT_TRUE(mock_timer.is_running); // 手动触发定时器到期,验证蜂鸣器状态切换逻辑 mock_timer.trigger_expiry(); EXPECT_TRUE(mock_pwm.is_toggled()); // 假设MockPWMLine提供状态检查方法 }
三、额外优化建议
- 抽象时钟接口:如果类中需要获取当前时间,同样封装
ClockInterface,测试时用MockClock返回预设时间。 - 解耦时间与业务逻辑:让
PassiveBuzzer仅负责蜂鸣器的PWM控制,时间调度逻辑完全交给外部定时器,降低类的职责复杂度。 - 借助测试框架工具:使用Google Test的GMock等工具,可以更高效地创建Mock对象、验证接口调用顺序和参数。
内容的提问来源于stack exchange,提问作者Roel Baardman
相关产品推荐
相关产品推荐

