2、测试驱动开发(TDD)入门:红-绿-重构循环
好,咱们正式开始聊TDD。说实话,我第一次接触TDD是在一个汽车电控项目里。当时团队被bug追着跑,加班加到怀疑人生。后来一位老外顾问引入了TDD,我一开始是抗拒的——「写测试?我连代码都写不完!」
但真用起来,我发现这玩意儿确实能救命。今天我就把TDD的核心套路掰开揉碎讲给你听。
2.1 红-绿-重构循环:TDD的节奏感
TDD说白了就三步:红 → 绿 → 重构。像个节拍器一样,一下一下地推着你往前走。
红(Red):先写一个会失败的测试。别急着写功能代码,先想清楚「我要测什么」。
绿(Green):写最简代码让测试通过。能跑就行,别管好不好看。
重构(Refactor):在测试保护下优化代码。这时候你改代码心里有底,因为测试会帮你兜底。
我习惯把这个循环叫做「TDD三拍子」。你想想看,写代码就像跳舞,没有节奏就容易乱。这个循环就是你的节奏。
2.2 为什么嵌入式开发也需要TDD?
很多人觉得嵌入式不适合TDD——「我跑在硬件上,怎么搞单元测试?」嗯,这个想法我当年也有过。
但后来我发现,恰恰是嵌入式更需要TDD。为什么?因为嵌入式代码一旦烧进去,调试成本太高了。你总不能每次改个逻辑都重新烧录、插示波器吧?
| 适用场景 | 说明 | 我的经验 |
|---|---|---|
| 算法逻辑 | PID控制、滤波算法、状态机 | 我在电机控制项目里,用TDD测PID参数,省了至少3天调试时间 |
| 协议解析 | CAN、Modbus、I2C数据包解析 | 曾经有个Modbus解析bug,用TDD半小时就定位了 |
| 业务逻辑 | 设备状态切换、告警逻辑 | 状态机用TDD写,覆盖率轻松到95%以上 |
| 硬件抽象层 | 通过mock模拟寄存器读写 | 这个稍复杂,但值得做 |
我的建议:别一上来就想测硬件。先把纯逻辑部分用TDD搞定,这部分收益最大。硬件相关的,等框架成熟了再慢慢加。
2.3 不适合TDD的场景
当然,TDD不是万能的。我踩过坑,所以得跟你说清楚。
- 硬件驱动初始代码:寄存器配置、时钟初始化这些,测起来太麻烦,性价比低
- 时序敏感代码:比如中断服务程序里的临界区,单元测试很难模拟时序
- 原型验证阶段:需求还没定,代码一天改三遍,这时候写测试就是浪费
避坑指南:我曾经在一个项目里,非要给所有硬件驱动写单元测试。结果测试代码比业务代码还长,维护成本爆炸。后来我学乖了——只测逻辑,不测硬件。
2.4 编写第一个失败的测试
好,咱们动手写代码。假设你要写一个简单的CRC校验函数。按照TDD的节奏,先写测试。
第一步:写一个会失败的测试。
// test_crc.c
#include "unity.h"
#include "crc.h"
void setUp(void) {}
void tearDown(void) {}
void test_crc8_should_return_0x00_for_empty_data(void)
{
uint8_t data[] = {};
uint8_t result = crc8(data, 0);
TEST_ASSERT_EQUAL_HEX8(0x00, result);
}
这时候你还没写 crc.h 和 crc.c,编译肯定报错。嗯,这就是「红」——测试失败,甚至编译都过不了。
第二步:写最简代码让测试通过。
// crc.h
#ifndef CRC_H
#define CRC_H
#include <stdint.h>
uint8_t crc8(const uint8_t *data, uint16_t len);
#endif
// crc.c
#include "crc.h"
uint8_t crc8(const uint8_t *data, uint16_t len)
{
return 0x00; // 先硬编码返回0,让测试通过
}
你看,我直接返回0x00。这代码看起来傻,但测试通过了!这就是「绿」。
第三步:重构。现在测试通过了,但代码显然不对。不过别急,先加更多测试用例,把逻辑逼出来。
void test_crc8_should_return_correct_for_single_byte(void)
{
uint8_t data[] = {0xAB};
uint8_t result = crc8(data, 1);
TEST_ASSERT_EQUAL_HEX8(0xAB, result); // 假设初始值为0,CRC8就是数据本身
}
这个测试会失败,因为当前 crc8 永远返回0。好,现在你有了两个测试:一个通过,一个失败。接下来你就要修改 crc8 的实现,让两个测试都通过。
关键点:每次只加一个测试,然后让这个测试通过。别贪多,一步一个脚印。
2.5 我常用的TDD工具链
嵌入式C单元测试,我推荐这几个工具:
- Unity:轻量级,纯C,适合嵌入式。我用了5年,没出过问题
- Ceedling:基于Unity的构建工具,自动管理测试和mock
- CMock:自动生成mock函数,适合测硬件抽象层
我个人习惯用Ceedling + Unity的组合。你想想看,一个命令就能跑所有测试,还能生成覆盖率报告,多省心。
2.6 小结
TDD的红-绿-重构循环,说白了就是「先想清楚要测什么,再写代码,最后优化」。这个习惯一旦养成,你会发现代码质量上了一个台阶。
下一章咱们会深入讲如何搭建嵌入式C的单元测试框架。到时候我会手把手带你配环境、写测试、跑覆盖率。嗯,敬请期待。