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.hcrc.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的单元测试框架。到时候我会手把手带你配环境、写测试、跑覆盖率。嗯,敬请期待。