4、测试用例设计基础:等价类划分、边界值分析、因果图法在中间件测试中的应用
各位同学,今天我们聊点实在的。
测试用例怎么设计?这个问题,我入行前三年一直在纠结。后来带团队了,发现新人问得最多的也是这个。说白了,测试用例设计就是「用最少的用例,发现最多的 bug」。
中间件测试尤其如此。你想想看,中间件夹在操作系统和应用之间,上下游关系复杂,输入输出千奇百怪。如果靠蛮力去测,测到天荒地老也测不完。
所以,必须讲方法。
今天我就把三个最经典的方法——等价类划分、边界值分析、因果图法——掰开揉碎了讲。每个方法我都会结合中间件测试的实际场景,把我踩过的坑也一并交代。
4.1 等价类划分:把无穷变成有限
等价类划分,说白了就是「分门别类,抽一个代表来测」。
举个例子。你测一个消息队列的发送接口,参数是消息体长度。理论上,长度可以是 0 到 2^32-1 之间的任意整数。你不可能每个值都测一遍吧?
等价类划分的思路是:把这些输入分成几个「等价类」,每个类里的数据,程序处理逻辑是一样的。然后从每个类里挑一个代表值来测。
在中间件测试中,我一般这样划分等价类:
| 等价类类型 | 说明 | 中间件测试示例 |
|---|---|---|
| 有效等价类 | 符合需求规格的输入 | 消息体长度 1~1024 字节 |
| 无效等价类 | 不符合需求规格的输入 | 消息体长度为 0,或超过 1024 字节 |
| 特殊等价类 | 空值、null、特殊字符等 | 消息体为 null,或包含不可见字符 |
我在项目中遇到过一个典型的例子。测试一个 RPC 框架的超时参数,需求说超时范围是 100ms~60000ms。我按等价类划分,选了 100ms(有效)、30000ms(有效)、60000ms(有效)、50ms(无效)、70000ms(无效)。结果呢?有效类全过了,无效类也正常报错。但上线后,有个客户设了 0ms,系统直接崩溃了。
4.2 边界值分析:bug 最爱藏在边界上
等价类划分解决了「测哪些值」的问题,但还不够。为什么?因为 bug 最喜欢藏在边界上。
你想想看,程序员写代码时,最容易写错的是什么?是 if (len <= 1024) 还是 if (len < 1024)?是 if (timeout >= 100) 还是 if (timeout > 100)?
边界值分析,就是专门对付这种「差之毫厘,谬以千里」的 bug。
边界值分析的核心思想:取边界值及其邻近值进行测试。
一般规则是:
- 如果输入范围是 [min, max],则测试 min、min+1、max-1、max,以及 min-1、max+1(如果允许)
- 如果是多值枚举,测试第一个、最后一个、中间一个,以及第一个的前一个、最后一个的后一个
拿刚才的消息队列例子来说,消息体长度范围是 1~1024 字节。边界值分析要测哪些?
// 边界值测试用例
// 有效边界
test_send_message(1); // 最小值
test_send_message(2); // 最小值+1
test_send_message(1023); // 最大值-1
test_send_message(1024); // 最大值
// 无效边界
test_send_message(0); // 最小值-1
test_send_message(1025); // 最大值+1
我个人习惯在边界值分析时,把「0」和「空」单独拎出来。很多中间件在处理空消息体时,行为跟长度为 0 的消息体还不一样。比如有的消息队列,空消息体会被当作心跳包,而长度为 0 的消息体就是普通消息。这两个边界,必须分开测。
4.3 因果图法:搞定复杂逻辑组合
等价类和边界值,对付的是单个输入。但中间件测试中,很多场景是多个输入组合在一起,产生不同的输出。这时候,因果图法就派上用场了。
因果图法,说白了就是「画一张图,把原因和结果的关系理清楚,然后生成测试用例」。
举个例子。测试一个分布式锁的获取接口。获取锁成功需要满足三个条件:
- 条件 A:锁名称不为空
- 条件 B:锁未被其他客户端持有
- 条件 C:超时时间在有效范围内(100ms~60000ms)
结果呢?
- 结果 X:获取锁成功
- 结果 Y:获取锁失败,返回错误码
这三个条件之间还有依赖关系:如果条件 A 不满足,直接返回错误,不再检查 B 和 C。这就是典型的「与」、「或」、「非」逻辑组合。
因果图法的步骤:
- 列出所有原因(输入条件):A、B、C
- 列出所有结果(输出):X、Y
- 画出因果图:用逻辑门(与、或、非)连接原因和结果
- 生成决策表:把因果图转换成表格形式
- 根据决策表设计测试用例
下面是我用 SVG 画的一个因果图示例,展示分布式锁获取逻辑:
根据这个因果图,我们可以生成决策表:
| 用例编号 | A: 锁名称 | B: 锁状态 | C: 超时 | 预期结果 |
|---|---|---|---|---|
| 1 | 非空 | 未持有 | 有效 | 获取成功 (X) |
| 2 | 非空 | 未持有 | 无效 | 获取失败 (Y) |
| 3 | 非空 | 已持有 | 有效 | 获取失败 (Y) |
| 4 | 非空 | 已持有 | 无效 | 获取失败 (Y) |
| 5 | 空 | — | — | 获取失败 (Y) |
我记得有一次测试一个消息路由模块,路由规则有 7 个条件,组合起来有 128 种可能。如果用全组合测试,128 个用例,测到吐血。用因果图法分析后,发现很多组合是冗余的,最终只用了 12 个用例就覆盖了所有逻辑分支。这就是因果图法的威力——用最少的用例,覆盖最多的逻辑路径。
4.4 三种方法的组合应用
讲到这里,你可能要问了:这三种方法,到底用哪个?
我的答案是:全用,但分场景。
- 单个输入参数:用等价类划分 + 边界值分析。先分有效/无效类,再取边界值。
- 多个输入参数组合:用因果图法。先画图,再生成决策表,最后设计用例。
- 复杂业务场景:三种方法结合。先用因果图梳理逻辑,再用等价类和边界值细化每个输入。
我一般的工作流是这样的:
- 拿到需求文档,先画因果图,理清所有输入输出的逻辑关系
- 对每个输入参数,做等价类划分,列出有效、无效、特殊等价类
- 对每个等价类,取边界值(包括边界两侧)
- 把边界值填入决策表,生成最终的测试用例
好了,今天的内容就到这里。等价类划分、边界值分析、因果图法,这三个方法看似简单,但真正用好,需要大量实践。我建议你从手头的中间件项目开始,挑一个接口,用这三种方法各设计一套用例,然后对比一下覆盖率和用例数量。你会发现,方法对了,事半功倍。