4、测试用例设计方法:等价类划分、边界值分析、判定表法、因果图法、场景法在嵌入式中的应用

说实话,很多刚入行的工程师觉得测试用例设计就是「随便点点,看跑不跑得通」。但在医疗嵌入式领域,这种想法很危险。我见过太多因为测试不充分导致的现场故障,轻则重启,重则...嗯,咱们还是聊技术吧。

这一章,我把自己十几年积累的测试用例设计经验拆开来讲。五种方法,每一种我都会结合嵌入式场景,说说它们到底怎么用、用在哪儿、有什么坑。

4.1 等价类划分:别把时间浪费在重复测试上

等价类划分,说白了就是把输入数据分成几组。同一组里的数据,程序处理逻辑是一样的。你测一个就够了,没必要全测。

举个例子:一个血压计的压力传感器,输入范围是0~300 mmHg。你想想看,测0、测150、测300,这三个值就能代表所有情况了吗?

其实可以。因为程序对0~300之间的值,处理逻辑完全一样。这就是一个有效等价类。那无效的呢?负数和大于300的值,各算一个无效等价类。

嵌入式场景要点:在嵌入式系统中,等价类划分不仅要考虑数值范围,还要考虑数据类型。比如ADC采样值,是8位还是12位?这决定了你的有效等价类边界在哪里。

我个人习惯,在划分等价类之前,先画一张输入输出关系表。把每个输入参数的可能取值列出来,然后分组。这样不容易漏。

避坑指南:我曾经在一个心电监护项目里,只划分了数值等价类,忘了考虑采样率这个参数。结果程序在50Hz采样率下跑得好好的,换到200Hz就崩了。从那以后,我每个输入参数都会单独做等价类划分。

4.2 边界值分析:嵌入式系统的命门

边界值分析,这是嵌入式测试里我最看重的方法。为什么?因为嵌入式系统里到处都是边界:定时器溢出、缓冲区满、队列空、ADC满量程...

你想想看,程序在边界处最容易出问题。比如一个环形缓冲区,写指针追上读指针时,是覆盖还是阻塞?这个判断逻辑,十有八九在边界处有bug。

具体做法:取每个等价类的边界值,以及边界值两侧的值。比如一个参数范围是1~100,那你要测:0、1、2、99、100、101。一共6个值。

边界类型 测试值 嵌入式场景举例
下边界-1 0 定时器计数值为0,是否立即触发中断?
下边界 1 队列中只有1个元素,出队后队列为空
下边界+1 2 缓冲区有2个字节,刚好凑成一个数据包
上边界-1 99 接近满量程,但还没到
上边界 100 满量程,ADC是否饱和?
上边界+1 101 超量程,程序如何处理?
注意:在嵌入式系统中,边界值分析一定要结合硬件特性。比如一个12位ADC,满量程是4095,不是4096。很多新手在这里栽跟头。

4.3 判定表法:理清复杂逻辑的利器

判定表法,适合处理多个条件组合的场景。比如一个输液泵,有「流速设置」「管路状态」「气泡检测」「压力报警」四个条件,每个条件有2~3种取值。组合起来可能有几十种情况。

你靠脑子想?想不过来的。画一张判定表,一目了然。

判定表的结构

  • 条件桩:列出所有输入条件
  • 动作桩:列出所有可能的输出动作
  • 条件项:每个条件的具体取值(Y/N)
  • 动作项:对应每个组合,应该执行的动作

我建议,在嵌入式项目中,判定表不要超过4个条件。超过4个,组合数太多,判定表会变得很庞大。这时候可以考虑用因果图法来简化。

我的经验:判定表法特别适合状态机测试。每个状态转换条件,都可以用一张判定表来描述。我在一个呼吸机项目中,用判定表覆盖了所有报警逻辑,上线后零报警误报。

4.4 因果图法:从原因推到结果

因果图法,说白了就是先画图,再转成判定表。它比判定表法多了一步:明确原因和结果之间的逻辑关系。

基本符号

  • 恒等:原因出现,结果就出现
  • :原因出现,结果不出现
  • :多个原因同时出现,结果才出现
  • :任意一个原因出现,结果就出现

举个例子:一个除颤仪的能量释放逻辑。原因有三个:充电完成、电极接触良好、操作员按下放电按钮。结果只有一个:释放能量。

这个逻辑是「与」关系:三个原因同时满足,才能释放能量。画成因果图,就是一个三输入的与门。

为什么会用到因果图?因为有些逻辑关系不是简单的组合,而是有依赖的。比如「充电完成」和「电极接触良好」之间可能有互斥关系。因果图能把这些关系清晰地表达出来。

嵌入式应用:因果图法特别适合测试安全关键功能。比如医疗设备的急停逻辑、报警逻辑、自检逻辑。这些逻辑通常有多个输入条件,且条件之间有复杂的依赖关系。

4.5 场景法:模拟真实使用流程

场景法,是我在嵌入式测试中最喜欢用的方法。为什么?因为它最贴近真实使用情况。

你想想看,用户不会按照你的测试用例来操作设备。他们会乱按、会同时按多个键、会在设备启动时拔电源...这些「异常场景」,等价类划分和边界值分析覆盖不到。

场景法的步骤

  1. 确定基本流:用户正常操作,设备正常响应
  2. 确定备选流:用户操作有变化,但仍在合理范围内
  3. 确定异常流:用户操作错误,或设备出现故障
  4. 组合场景:基本流 + 备选流 + 异常流,组合成完整的测试场景

举个例子,一个血糖仪的基本流是:插入试纸 → 滴血 → 等待测量 → 显示结果。备选流可能是:插入试纸后,用户又拔出来重新插。异常流可能是:滴血量不足,设备报错。

我建议,每个功能模块至少设计3~5个场景。一个基本流,两个备选流,两个异常流。这样基本能覆盖90%的使用情况。

避坑指南:我曾经在一个注射泵项目中,只测了基本流和备选流,忽略了异常流。结果上线后,用户在一次输液过程中按了暂停键,然后又快速按了启动键,设备直接死机了。原因是暂停和启动之间的状态转换没处理好。从那以后,我每个场景都会专门设计「快速连续操作」的异常流。

4.6 五种方法的组合使用策略

在实际项目中,这五种方法不是孤立使用的。我一般这样组合:

测试阶段 推荐方法 说明
单元测试 等价类划分 + 边界值分析 针对单个函数或模块,覆盖输入输出
集成测试 判定表法 + 因果图法 针对模块间的交互逻辑
系统测试 场景法 模拟真实使用流程
回归测试 边界值分析 + 场景法 重点覆盖修改过的边界和场景

我个人习惯,在写测试用例之前,先画一张「测试策略矩阵」。横轴是功能模块,纵轴是测试方法。每个格子填上测试优先级(高/中/低)。这样能确保每个功能模块都被合适的测试方法覆盖到。

小技巧:在嵌入式系统中,测试用例的执行顺序也很重要。我一般先跑边界值分析,因为边界最容易出问题。然后跑等价类划分,验证正常功能。最后跑场景法,模拟真实使用。这样能快速发现严重问题,提高测试效率。

嗯,这一章的内容就到这里。五种方法,每一种都有它的适用场景。关键是要根据你的项目特点,灵活组合使用。记住,测试不是为了证明程序没有bug,而是为了发现bug。方法用得对,bug跑不掉。