单元测试基础理论

好,咱们正式开始聊单元测试的基础理论。这部分内容,说白了就是地基。地基打不牢,后面盖再高的楼也悬。我个人习惯,每接手一个新项目,第一件事不是看代码,而是先想清楚:这个模块的单元测试该怎么测?

单元测试的定义

单元测试,听起来挺唬人,其实没那么复杂。它就是对软件中的最小可测试单元进行检查和验证。在嵌入式世界里,这个“最小单元”通常是一个函数,或者一个模块的接口。

我举个例子你就明白了。比如你写了一个计算CRC校验的函数:

uint32_t calculate_crc(uint8_t *data, uint32_t len);

单元测试要做的,就是给这个函数喂不同的数据,看它吐出来的CRC值对不对。就这么简单。但为什么很多人做不好?因为边界条件没想全。

核心要点:单元测试不是测试整个系统,而是测试一个独立的功能点。它关注的是“这个函数对不对”,而不是“整个系统跑不跑得起来”。

我在项目中遇到过一件事:有个同事写了个温度传感器驱动,单元测试全过了,结果一上硬件就崩。后来一查,问题出在I2C通信的超时处理上——他的单元测试根本没覆盖到总线挂死的情况。你看,这就是单元测试没做到位。

测试金字塔

测试金字塔这个概念,我觉得每个嵌入式工程师都应该刻在脑子里。它长这样:

层级 名称 数量 执行速度 维护成本
顶层 端到端测试
中间层 集成测试 适中 中等 中等
底层 单元测试

为什么叫金字塔?因为底层的单元测试应该最多,越往上越少。你想想看,如果每个函数都测好了,集成的时候出问题的概率是不是就小很多?

我曾经在一个医疗内窥镜项目上吃过亏。团队为了赶进度,单元测试写得很少,直接跳到集成测试。结果呢?每次集成都要花两三天去定位问题,最后发现都是些低级错误——指针没判空、数组越界这些。如果单元测试做扎实了,这些bug在写代码当天就能发现。

我的建议:单元测试、集成测试、端到端测试的比例,我一般按7:2:1来分配。70%的精力放在单元测试上,这是性价比最高的投入。

白盒测试与黑盒测试

这两个概念,很多新手容易搞混。我换个说法你就懂了。

白盒测试

白盒测试,说白了就是“打开盒子看里面”。你清楚代码的内部逻辑,知道每个分支、每个循环、每个条件判断。测试的时候,你就盯着这些内部结构去设计用例。

比如这个函数:

int8_t check_battery_level(uint16_t voltage) {
    if (voltage > 4200) {
        return BATTERY_OVER_VOLTAGE;
    } else if (voltage < 3000) {
        return BATTERY_UNDER_VOLTAGE;
    } else {
        return BATTERY_NORMAL;
    }
}

白盒测试会怎么测?它会覆盖三个分支:电压过高、电压过低、正常范围。还要测边界值:4200、3000、4199、3001这些点。嗯,这里要注意,边界值是最容易出bug的地方。

黑盒测试

黑盒测试就不一样了。你把这个函数当成一个黑盒子,不管里面怎么实现的,只看输入输出对不对。你给它4201,它返回BATTERY_OVER_VOLTAGE,这就对了。

黑盒测试的好处是什么?它不依赖代码实现。哪怕你明天把if-else改成switch-case,只要接口不变,测试用例就不用改。

对比项 白盒测试 黑盒测试
关注点 内部逻辑结构 外部功能行为
测试依据 源代码 需求规格说明
覆盖率 语句、分支、路径 等价类、边界值
适用阶段 单元测试 系统测试

避坑指南:我曾经犯过一个错误——只做白盒测试,不做黑盒测试。结果代码覆盖率100%,但功能就是不对。为什么?因为需求理解错了。白盒测试只能保证“代码按你的意图跑”,不能保证“你的意图是对的”。所以,两种测试要结合着来。

在实际的内窥镜项目中,我通常这样分配:单元测试阶段以白盒为主,黑盒为辅。白盒保证代码逻辑全覆盖,黑盒保证功能需求全满足。你想想看,一个图像处理函数,白盒能帮你发现数组越界,黑盒能帮你验证输出图像是不是清晰——两者缺一不可。

好了,单元测试的基础理论就聊到这儿。记住三句话:单元测试测的是最小单元,测试金字塔告诉我们精力怎么分配,白盒黑盒要双管齐下。下一章咱们聊聊具体的测试框架怎么选,到时候我会分享一些实际踩过的坑。