一、单元测试基础概念

什么是单元测试

单元测试,说白了就是给代码做「最小粒度的体检」。

我习惯这样定义它:单元测试是针对软件中最小可测试单元(函数、方法、模块)的验证活动。在嵌入式世界里,这个「最小单元」通常是一个 C 函数、一个静态方法,或者一个中断服务例程。

举个例子,你写了一个计算 CRC 校验的函数:

uint16_t calc_crc16(const uint8_t *data, uint32_t len);

单元测试要做的就是:给它一组已知输入,检查输出是不是你期望的 CRC 值。就这么简单。

核心特征:单元测试是白盒的、自动化的、可重复的。它不依赖硬件,不依赖外设,甚至不依赖操作系统。

为什么需要单元测试

你可能觉得:「我代码跑起来没问题啊,干嘛多此一举?」

嗯,我以前也这么想。直到有一次,我在一个电机控制项目里改了一行滤波参数,结果三天后才发现另一处逻辑被带偏了。排查花了两天,改回来只花了五分钟。

单元测试的价值,我总结为四点:

  • 尽早发现缺陷 —— 编码阶段就能揪出逻辑错误,不用等到集成时炸锅
  • 降低修复成本 —— 缺陷发现越晚,修复代价越高,这是铁律
  • 保障重构安全 —— 你敢不敢改一段三年前的代码?有单元测试在,你就敢
  • 形成可执行文档 —— 测试用例本身就是最好的使用说明

我的经验:嵌入式项目里,硬件环境越复杂,单元测试的回报率越高。你想想看,在硬件还没回来的时候就能验证大部分逻辑,这能省多少时间?

单元测试 vs 集成测试

很多人分不清这两个概念。我直接画个对比表,一目了然:

对比维度 单元测试 集成测试
测试对象 单个函数/模块 多个模块的交互
执行环境 主机(PC) 目标板或仿真器
依赖关系 可隔离(打桩/模拟) 真实依赖
执行速度 毫秒级 秒级甚至分钟级
发现问题类型 逻辑错误、边界条件 接口不匹配、时序问题
谁来做 开发工程师 测试工程师或集成工程师

说白了,单元测试管的是「每个零件合不合格」,集成测试管的是「零件装在一起能不能转」。两个都重要,但顺序不能乱。

我曾经踩过的坑:在一个车载项目中,团队跳过单元测试直接做集成测试。结果每次集成都炸,定位问题要花大量时间。后来补上单元测试,集成一次通过的几率从 30% 提升到了 85%。

嵌入式单元测试的特殊性

嵌入式软件做单元测试,跟纯软件项目不太一样。我列几个关键点:

  • 硬件耦合 —— 寄存器操作、中断、外设访问,这些都需要打桩
  • 资源受限 —— 目标板上跑不了测试框架,得在 PC 上交叉编译测试
  • 实时性要求 —— 有些逻辑跟时间强相关,测试时要模拟时序
  • 编译器差异 —— 主机编译器跟交叉编译器行为可能不同

你想想看,一个操作寄存器的函数,在目标板上跑没问题,但在 PC 上编译就报错。怎么办?这就需要我们做「硬件抽象层」的打桩处理。这部分后面章节会详细讲。

本章知识体系

下面这张图,把单元测试的核心概念串起来了:

单元测试知识体系 单元测试 定义 最小可测单元验证 价值 早发现·低成本·保重构 vs 集成测试 零件 vs 整机 嵌入式特性 硬件耦合·资源受限 核心原则 隔离外部依赖 · 自动化执行 · 快速反馈 先写测试再写代码(TDD) · 测试即文档

什么时候开始做单元测试

我的建议是:从项目第一天就开始。

别等到代码写完了再补测试,那时候你根本没动力。我个人的习惯是:每写完一个函数,顺手就把它的测试用例写了。花不了几分钟,但后面能省大把时间。

实用技巧:刚开始不用追求 100% 覆盖率。先把核心逻辑、边界条件、异常分支覆盖到,剩下的慢慢补。80% 的覆盖率比 0% 强一百倍。