3. 测试用例设计方法:等价类划分、边界值分析、因果图法、场景法在嵌入式中的应用
做嵌入式测试这些年,我最大的体会是:测试用例设计不是拍脑袋。你想想看,一个嵌入式系统可能有几十个输入参数、上百种状态组合。靠直觉去测,漏掉关键场景是迟早的事。
这一章,我把最常用的四种方法掰开揉碎讲清楚。每种方法我都会结合嵌入式场景,告诉你什么时候用、怎么用、坑在哪。
核心观点:没有一种方法能覆盖所有场景。等价类划分负责“减量”,边界值分析负责“找茬”,因果图法负责“理关系”,场景法负责“串流程”。四者配合,才能构建完整的测试用例体系。
3.1 等价类划分:把无限输入变成有限测试
等价类划分,说白了就是把输入域分成若干“等价”的组。同一组里的数据,程序处理逻辑是一样的。你只需要从每组里挑一个代表来测就行。
我在项目中遇到过这样一个案例:一个温度采集模块,输入范围是 -40°C 到 125°C。如果按每个整数点去测,要测 166 个点。用等价类划分,只需要 3 个:
| 等价类 | 范围 | 代表值 | 预期结果 |
|---|---|---|---|
| 有效等价类(正常范围) | -40 ~ 125 | 25 | 正常采集并上报 |
| 无效等价类(低于下限) | < -40 | -50 | 返回错误码或默认值 |
| 无效等价类(高于上限) | > 125 | 130 | 返回错误码或默认值 |
嗯,这里要注意:无效等价类往往比有效等价类更容易发现 bug。我见过太多工程师只测正常情况,结果产品在现场一遇到异常输入就死机。
我的习惯:每个输入参数至少划分 1 个有效等价类和 2 个无效等价类(低于下限、高于上限)。如果参数有特殊格式(如 MAC 地址、IP 地址),还要增加格式错误的等价类。
3.2 边界值分析:bug 最喜欢藏在边界上
为什么边界值分析这么重要?因为程序员写条件判断时,最容易在边界上犯错。比如 if (temp >= -40 && temp <= 125),少写一个等号,边界值就出问题了。
边界值分析的核心原则:取边界点、边界内邻点、边界外邻点。还是以温度采集为例:
| 边界位置 | 测试值 | 说明 |
|---|---|---|
| 下边界 | -40 | 刚好在边界上 |
| 下边界内邻点 | -39 | 边界内第一个有效值 |
| 下边界外邻点 | -41 | 边界外第一个无效值 |
| 上边界 | 125 | 刚好在边界上 |
| 上边界内邻点 | 124 | 边界内最后一个有效值 |
| 上边界外邻点 | 126 | 边界外第一个无效值 |
我曾经在一个 UART 通信驱动里,就因为边界值没测到位,导致波特率配置在 115200 时偶发丢数据。排查了两天才发现,是分频系数计算在边界处溢出了。从那以后,边界值分析成了我写测试用例的必选项。
避坑指南:边界值分析不只看数值边界,还要看时间边界、计数边界、缓冲区边界。比如看门狗超时时间、环形缓冲区的读写指针、DMA 传输长度——这些地方都是 bug 高发区。
3.3 因果图法:理清多条件组合的逻辑
当输入条件之间有逻辑关系时,等价类和边界值就不够用了。比如一个电机控制系统:
- 条件 1:温度正常(T)
- 条件 2:电流正常(C)
- 条件 3:急停按钮未按下(E)
- 结果:电机启动(M)
逻辑关系是:M = T AND C AND E。用因果图法,我们可以画出因果关系,然后生成决策表:
| 条件/结果 | 规则1 | 规则2 | 规则3 | 规则4 | 规则5 | 规则6 | 规则7 | 规则8 |
|---|---|---|---|---|---|---|---|---|
| T(温度正常) | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| C(电流正常) | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
| E(急停未按下) | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
| M(电机启动) | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
你看,8 种组合里只有 1 种能让电机启动。如果不画因果图,你可能会漏掉“温度异常但电流正常”这种场景。
我个人习惯是:当条件数在 3~6 个之间时,因果图法效率最高。条件太少直接用等价类就行,条件太多(超过 7 个)组合爆炸,建议拆分成多个子功能分别分析。
3.4 场景法:用真实流程串联测试
前面三种方法都是“静态”的——它们关注的是输入和输出的关系。但嵌入式系统是“动态”的,有状态转换、有流程流转。这时候就需要场景法。
场景法的核心是:识别用户的操作路径,覆盖基本流和备选流。以智能门锁为例:
- 基本流:用户输入正确密码 → 门锁打开 → 用户进门 → 门锁自动上锁
- 备选流 1:用户输入错误密码 → 提示重新输入 → 连续 3 次错误 → 锁定 30 秒
- 备选流 2:用户使用指纹开锁 → 指纹匹配 → 门锁打开
- 备选流 3:低电量报警 → 用户使用机械钥匙开锁 → 更换电池
- 备选流 4:门锁被暴力撬动 → 触发报警 → 发送通知到手机
每个场景都要设计测试用例。我建议用场景-用例矩阵来管理:
| 场景编号 | 场景描述 | 前置条件 | 测试步骤 | 预期结果 |
|---|---|---|---|---|
| S01 | 正常密码开锁 | 门锁处于待机状态 | 输入正确密码 → 按确认键 | 门锁打开,蜂鸣器响一声 |
| S02 | 密码错误 3 次锁定 | 门锁处于待机状态 | 连续输入 3 次错误密码 | 第 3 次后屏幕显示“锁定 30s” |
| S03 | 低电量开锁 | 电池电量 < 10% | 输入正确密码 | 门锁打开,同时屏幕闪烁低电量图标 |
我曾经在一个车载 T-Box 项目里,用场景法发现了 OTA 升级流程中的死锁问题。基本流测了 10 遍都没问题,但备选流“升级过程中网络中断”这个场景,一测就复现了。所以我的建议是:基本流保证功能可用,备选流保证系统健壮。
3.5 四种方法的协同使用
这四种方法不是孤立的。我通常这样组合:
- 先用场景法:梳理出用户的操作流程和系统状态转换
- 再用因果图法:分析每个场景中的条件组合逻辑
- 然后用等价类划分:对每个输入参数分组,减少测试数量
- 最后用边界值分析:在等价类的基础上,补充边界测试
下面这张图展示了它们之间的关系:
3.6 嵌入式场景的特殊考量
嵌入式测试和纯软件测试不一样。有几点我要特别提醒:
- 硬件约束:有些输入值在软件层合法,但硬件不支持。比如 ADC 采样值,软件定义范围是 0~4095,但硬件实际只能到 4080。边界值要按硬件实际能力来取。
- 时序约束:嵌入式系统对时间敏感。等价类划分时,要把“时间”也当作一个输入参数。比如按键消抖时间 20ms,边界值要测 19ms、20ms、21ms。
- 资源约束:内存、Flash、栈空间都是有限的。场景法要包含“资源耗尽”的场景,比如内存分配失败、栈溢出。
- 中断干扰:因果图法要考虑中断对条件的影响。比如一个条件在判断过程中被中断改变了,结果会怎样?
我曾经踩过的坑:在一个传感器驱动项目中,我用等价类划分把温度值分成了 3 组,边界值也测了。结果现场发现,温度在 0°C 附近时,传感器输出的值会跳变。原因是传感器本身在 0°C 附近有非线性区。从那以后,我养成了一个习惯:等价类划分之前,先看芯片手册,了解硬件的行为特性。
3.7 实战案例:温控系统的测试用例设计
最后,用一个完整的案例把四种方法串起来。假设我们要测试一个温控系统:
- 输入:当前温度(-20°C ~ 80°C)、目标温度(0°C ~ 60°C)、工作模式(加热/制冷/自动)
- 输出:加热器控制信号、制冷器控制信号、报警信号
- 逻辑:自动模式下,当前温度低于目标温度-2°C 时开启加热,高于目标温度+2°C 时开启制冷
第一步:场景法
- 基本流:设置目标温度 → 系统自动调节 → 达到目标温度 → 保持恒温
- 备选流 1:温度传感器故障 → 系统报警并关闭输出
- 备选流 2:目标温度设置超出范围 → 提示错误并保持原设置
- 备选流 3:手动切换工作模式 → 系统立即切换控制策略
第二步:因果图法
分析自动模式下的控制逻辑:
- 条件 A:当前温度 < 目标温度 - 2°C
- 条件 B:当前温度 > 目标温度 + 2°C
- 结果 X:开启加热器
- 结果 Y:开启制冷器
- 逻辑:X = A AND NOT B,Y = NOT A AND B
第三步:等价类划分
| 参数 | 有效等价类 | 无效等价类 |
|---|---|---|
| 当前温度 | -20 ~ 80 | < -20, > 80 |
| 目标温度 | 0 ~ 60 | < 0, > 60 |
| 工作模式 | 加热、制冷、自动 | 其他值(如 0xFF) |
第四步:边界值分析
重点关注温差边界:
- 当前温度 = 目标温度 - 2°C(刚好触发加热)
- 当前温度 = 目标温度 - 1°C(不触发加热)
- 当前温度 = 目标温度 - 3°C(应该触发加热)
- 当前温度 = 目标温度 + 2°C(刚好触发制冷)
- 当前温度 = 目标温度 + 1°C(不触发制冷)
- 当前温度 = 目标温度 + 3°C(应该触发制冷)
你看,四种方法配合下来,测试用例既全面又高效。没有冗余,也没有遗漏。
我的建议:刚开始用这些方法时,可能会觉得麻烦。但坚持用 2~3 个项目,你就会发现:测试用例的质量提升了,返工率下降了,定位 bug 的时间也缩短了。这些方法不是束缚,而是工具。