做测试这十来年,被问得最多的一句话就是"用例怎么设计才算够"。新人上手通常是拿到需求文档,打开表格,从第一条需求开始往下写:输入框能输什么、按钮点了会怎样、页面跳不跳。这么写出来的用例,跑起来也能发现 bug,但只要版本一大、逻辑一复杂,问题就暴露了——要么漏得离谱,要么写了八百条互相重复的用例把回归时间撑到三个小时。真正把用例质量拉开差距的,不是写得多快,而是脑子里有没有一套成体系的测试用例设计方法。等价类划分、边界值分析、判定表、因果图、正交实验法、场景法、错误推测法,这七个方法不是七个需要背下来的知识点,而是七把不同用途的工具,用对了能把无穷的输入组合压成几十条高价值的用例,用错了就是白写。这篇内容面向的是刚入行一两年的测试同学,以及需要给团队做用例评审的负责人,我会把七个方法逐个拆开讲清楚"什么时候用、怎么用、容易在哪翻车",每个方法都配一个能直接抄的案例,最后附上我这些年踩坑攒下来的排查速查表和经验条目。
1. 七个方法不是知识点清单,而是一套分工体系
1.1 从"照着需求写"到"按方法设计",中间差的是什么
很多人写用例的默认动作是"翻译":需求写了一句"金额输入框支持 0.01 到 50000 元",就写一条"输入 100,提交成功"。这条用例当然没错,但它对一个字段的覆盖几乎是零。字段的取值域是连续的,你不可能穷举,但你可以用等价类把连续域切成若干段,用边界值把切缝上的三个点钉住,这样十几条用例就能覆盖住这个字段 95% 以上的风险面。这就是"设计"和"翻译"的区别——设计是有意识地做取舍,翻译只是机械搬运。
另一个常见的差距在于可追溯性。按方法设计出来的用例,每条都能回答"我为什么写这条":它覆盖的是哪个等价类、哪条判定规则、哪条状态迁移路径。评审的时候,别人问"这个场景为什么没测",你能立刻指出它属于哪个已经覆盖的等价类,或者明确指出这是本次范围外的风险。而按需求逐句翻译出来的用例,被问到漏测时只能回答"文档里没写",这句话在评审会上基本等于承认失职。
所以我把这七个方法的价值总结成一句话:它们让你在有限的时间和用例条数里,做出可解释、可评审、可复用的覆盖决策。
1.2 七个方法各自的主战场
先给一张全景图,后面再逐个展开。这七个方法可以粗分成三类:
- 数据类方法:等价类划分、边界值分析。处理对象是单个或少数几个输入字段的取值域,目标是"用最少的用例覆盖最多的取值"。
- 逻辑类方法:判定表、因果图。处理对象是多个条件之间的组合逻辑,目标是"把条件组合和对应动作一一对齐,不漏不多"。
- 流程与经验类方法:正交实验法、场景法、错误推测法。正交处理多因子多水平造成的组合爆炸,场景法处理端到端的业务链路,错误推测法负责用经验去补前面六种方法都覆盖不到的边角。
这个分类很重要,因为最常犯的错误就是拿错工具。比如一个"会员等级 × 券类型 × 是否首单 × 支付渠道"的优惠计算逻辑,有人上来就用等价类划分,把每个条件单独拆开测一遍。单条件确实都过了,但"会员 + 首单 + 折扣券"这种组合触发的叠加 bug 根本测不到。这类问题必须用判定表或因果图。反过来,一个只有上下限的年龄输入框,你去做判定表就是杀鸡用牛刀,四条规则里有两条还是互斥的。
1.3 方法选型的判断依据
我在团队里给新人总结过一张选型表,照着输入特征对号入座基本不会错:
| 输入特征 | 首选方法 | 主要产出 | 常见误用 |
|---|---|---|---|
| 单字段有明确取值域或取值范围 | 等价类划分 + 边界值分析 | 有效/无效等价类表、边界点用例 | 只测有效值,无效值一条不写 |
| 多个条件共同决定一个结果 | 判定表 | 条件桩、动作桩、规则列 | 条件写得太粗,规则列漏项 |
| 条件之间有明确因果依赖、约束 | 因果图 | 因果图 + 判定表转换 | 图只画不转,画完就丢 |
| 3 个以上因子,每个因子多个水平 | 正交实验法 | 正交表、因子水平表 | 盲目上全组合,用例量失控 |
| 端到端业务流程、跨模块交互 | 场景法 | 基本流、备选流、异常流用例 | 只写正常流,异常流靠现场临时想 |
| 需求模糊、历史 bug 高发模块 | 错误推测法 | 经验用例清单 | 当成主方法用,覆盖面不可控 |
| 对象有明确的状态流转 | 状态迁移法 | 状态迁移图、路径覆盖表 | 只测正向流转,回退和非法迁移不测 |
提示:一张表里出现四种以上输入特征时,不要只挑一个方法。通常是数据类方法处理字段、判定表处理规则、场景法串流程,三层叠起来才完整。
选型只是一半,另一半是顺序。我的习惯是先做场景法把主干流程铺出来,让整个测试范围有个骨架;再用判定表和因果关系把每个业务节点里的规则逻辑补上;最后用等价类、边界值把每个具体字段的取值打磨一遍;收尾的时候拿错误推测法扫一遍历史 bug 清单。这个顺序走下来,用例集既有骨架又有血肉,评审的时候也容易讲清楚。
2. 等价类划分与边界值分析:把无穷输入压成有限用例
2.1 等价类划分的实操逻辑
等价类划分的核心假设是:如果某个输入值能触发缺陷,那么它所在的整个取值范围里,其他值大概率也能触发同一个缺陷。所以只要从每一类里挑一个代表值测一次就够了。这句话说起来简单,实操时最容易出问题的是划分的粒度。
举个例子,一个提现金额输入框,需求写的是"单笔提现 0.01 元至 50000 元,且不能超过可用余额"。很多人会划成两个等价类:有效(0.01 ~ 50000)、无效(其他)。这个划法漏掉了什么?漏掉了"格式类无效"和"业务类无效"的区别。输入-5、abc、0、100.999、1e3、空格、超长数字串,这些在代码里走的是完全不同的校验分支——有的是前端正则拦掉,有的是后端 BigDecimal 解析报错,有的是业务规则拒绝。它们的错误提示文案、日志记录、是否触发风控,全都不一样。把它们笼统归成"无效等价类",只测一个-5,等于放掉了六七个分支的验证机会。
我的做法是把无效等价类至少拆成三层:格式无效(非数字、超长、特殊字符、科学计数法)、边界无效(0、负数、超上限)、业务无效(金额超过可用余额、小于手续费、超过单日限额)。每一层再配合边界值方法补充具体点位。收尾的时候拿错误推测法扫一遍历史 bug 清单。这个顺序走下来,用例集既有骨架又有血肉,评审的时候也容易讲清楚。
另外提醒一点,划分等价类一定要看代码或者接口定义,不要只看需求文档。需求说"金额最多两位小数",代码里用的是setScale(2, RoundingMode.HALF_UP),那输入0.005到底是拒绝还是进位成0.01?只有看代码才知道。这类"需求没写、代码有实现"的分支,是用例设计里性价比最高的富矿。
2.2 边界值不是"取最大最小值"那么简单
边界值分析经常被简化成"取最小值、最大值、比最小值小一点、比最大值大一点",这个理解只对了一半。边界点的选取要跟着等价类的切分方式走,而切分方式取决于代码里的判断符号是<还是<=。
假设上限是 50000,代码写的是if (amount > 50000) reject;,那 50000 本身是合法的,边界点应该取49999、50000、50001。如果代码写的是if (amount >= 50000) reject;,那 50000 就是非法的,边界点变成49999、50000,最后一个点是非法值。这两种写法的 bug 只有在取到精确点位时才会暴露,测 49999 和 50001 都发现不了。
还有一种更隐蔽的边界:精度边界。金额字段上限 50000、两位小数,那49999.99是合法最大值,50000.00是边界,50000.01越界。同时还要测0.01、0.001(三位小数是否被截断或拒绝)、0.004(四舍五入是否变成 0)、0.005(是进位还是拒绝)。这几个点位测完,基本能把金额字段的坑挖干净。
我把边界值常用的取点策略整理成一张表,直接照着填:
| 边界类型 | 取点 | 典型意义 |
|---|---|---|
| 数值范围 | min-1, min, min+1, max-1, max, max+1 | 验证<与<=的差异 |
| 精度 | 最小精度 ± 半个精度单位 | 验证舍入策略 |
| 长度 | len-1, len, len+1 | 验证截断与超长拒绝 |
| 循环次数 | n-1, n, n+1 | 验证分页、批处理阈值 |
| 时间 | 临界时间点前后一分钟 | 验证定时任务、有效期判断 |
2.3 案例分析:提现金额字段的完整用例集
把上面两个方法合起来用,一个提现金额字段的用例集大概长这样:
| 编号 | 输入值 | 覆盖的等价类/边界 | 预期结果 |
|---|---|---|---|
| TC-01 | 100.00 | 有效:正常区间 | 提交成功,进入确认页 |
| TC-02 | 0.01 | 有效边界:下限 | 提交成功 |
| TC-03 | 49999.99 | 有效边界:上限内侧 | 提交成功 |
| TC-04 | 0 | 无效边界:下限外 | 提示金额需大于 0 |
| TC-05 | -1 | 无效:负数 | 提示金额格式错误 |
| TC-06 | 50000.00 | 无效边界:上限 | 提示超出单笔限额 |
| TC-07 | 50000.01 | 无效边界:上限外 | 提示超出单笔限额 |
| TC-08 | 0.001 | 无效:精度超限 | 提示最多两位小数 |
| TC-09 | 0.005 | 精度边界:舍入 | 需确认舍入策略后断言 |
| TC-10 | abc | 无效:非数字 | 前端拦截,不发起请求 |
| TC-11 | 1e5 | 无效:科学计数法 | 前端拦截或解析失败 |
| TC-12 | 空字符串 | 无效:空值 | 提示必填 |
| TC-13 | 仅空格 | 无效:空白字符 | 提示必填 |
| TC-14 | 余额 + 1 | 无效:业务上限 | 提示超出可用余额 |
| TC-15 | 余额 | 业务边界:等于余额 | 提交成功 |
十五条用例覆盖了一个字段,看起来不少,但比"输入 100 提交成功、输入 abc 报错"两条用例强了不止一个量级。这十五条里,TC-04、TC-06、TC-09、TC-14 是我在实际项目里真正抓到过 bug 的,尤其是 TC-09 那个舍入点,前后端用的舍入模式不一致,前端显示 0.01、后端存成 0,对账直接对不平。
注意:边界值用例的断言一定要精确到"提示文案 + 是否发起请求 + 数据库落库值"三个层面。只断言"页面弹出错误提示",很可能漏掉"虽然提示了错误但还是发了一次请求"这种幂等性问题。
3. 判定表与因果图:多条件组合下的不漏不炸
3.1 判定表的四要素与化简
判定表是处理组合逻辑最稳的工具,它把"条件"和"动作"放在一张表里,每一列代表一条规则。标准结构包含四部分:条件桩(列出所有条件)、动作桩(列出所有可能动作)、条件项(每个条件在各规则下的取值)、动作项(每个规则下执行哪些动作)。
还是那句提醒:判定表的难点不在画,在条件桩的提炼。条件写粗了,规则列会漏;条件写细了,规则数会指数爆炸。我的经验是先把条件按"是否真正影响结果"筛一遍,把不影响结果的条件剔除。比如一个优惠券计算逻辑,最初列了七个条件,包括用户注册时间、是否绑定手机号,实际推下来这两个条件对优惠结果毫无影响,直接从条件桩里删掉,规则数从 128 列降到 32 列。
第二个化简手段是利用无关项。当某个条件的取值不影响最终动作时,条件项里填-,这一列可以和其他列合并。上面 32 列经过无关项合并,通常能压到 8 到 12 列,这才是可执行的规模。
3.2 因果图用在哪,什么时候可以跳过
因果图的价值在于处理条件之间的约束关系,常见的有四类:
- 互斥(E):两个原因不能同时成立,比如"普通用户"和"会员用户"。
- 包含(I):至少有一个原因成立,比如"手机号登录"或"邮箱登录"至少走一个。
- 唯一(O):有且仅有一个成立。
- 要求(R):一个原因成立时另一个必须成立,比如"用了折扣券"要求"订单金额达标"。
判定表本身表达不了这些约束,硬画会画出大量实际不可能出现的规则列。因果图的作用就是先把约束标出来,再转成判定表时把这些无效列直接划掉。
不过说实话,实际项目里我很少完整画因果图。原因很现实:画图工具不统一、评审时没人愿意看图、需求变更后图很难维护。更常见的做法是直接列判定表,然后在条件桩旁边用注释标明约束关系,比如"注:C1 与 C2 互斥,不出现同真组合"。效果等价,维护成本低得多。因果图我会在两种情况下真的画:一是逻辑复杂到用表格描述不清,二是需要跟产品经理对齐需求理解的时候,一张图比十行表格更容易达成共识。
3.3 案例分析:优惠券叠加规则
需求描述:订单金额满 100 元可用满减券;会员在满减券基础上可再叠加会员折扣;折扣券与满减券不可叠加;不满足门槛时提示不满足使用条件。
条件桩:
- C1:订单金额 ≥ 100
- C2:用户为会员
- C3:持有满减券
- C4:持有折扣券
动作桩:
- A1:应用满减券
- A2:应用会员折扣
- A3:应用折扣券
- A4:提示不满足使用条件
化简后的判定表:
| 规则 | R1 | R2 | R3 | R4 | R5 | R6 |
|---|---|---|---|---|---|---|
| C1 金额≥100 | Y | Y | Y | Y | N | N |
| C2 会员 | Y | Y | N | N | - | - |
| C3 满减券 | Y | N | Y | N | Y | Y |
| C4 折扣券 | N | Y | N | Y | - | - |
| A1 应用满减券 | X | X | ||||
| A2 应用会员折扣 | X | X | ||||
| A3 应用折扣券 | X | X | ||||
| A4 提示不满足 | X | X |
六个规则列对应六条用例。R1 是最容易出 bug 的:满减券和会员折扣的叠加顺序会直接决定最终价格,先减后折和先折后减结果不一样,而且往往差几分钱到几块钱,这个差异只有测出来才会被发现。R5 和 R6 是回归时最容易被砍掉的两条,因为"不满足条件"看起来不涉及金额计算,但实际项目里我遇到过门槛判断用的是优惠前金额还是优惠后金额不一致导致的越权使用,就是靠这两条抓出来的。
提示:判定表的每一列都要落成一条可执行用例,并且用例标题里写清楚规则编号,比如"TC-R1 满减券+会员订单金额≥100 应同时减免"。回归时被砍掉哪条规则一目了然。
4. 正交实验法与场景法:组合爆炸和端到端流程的两把刀
4.1 正交表的选型与因子水平计算
组合测试的痛点是乘法效应。三个因子,每个三个水平,全组合是 3×3×3 = 27 种;四个因子就是 81 种;上了五个因子、每个四个水平,直接是 1024 种。你不可能全测,无脑抽样又容易漏掉两两组合的交互缺陷。正交实验法解决的就是这个问题:它用精心构造的表,让任意两个因子的所有水平组合都至少出现一次,而用例总数远小于全组合。
常见的正交表记法是L9(3^4),含义是 9 行、每个因子 3 个水平、最多容纳 4 个因子。注意"最多 4 个因子"这个说法,如果只有 3 个因子,用这张表的前三列就行,多余的列空着不写。
三个因子取 3 个水平时,全组合 27 条,正交后 9 条,压缩率 67%。下面是一张L9(3^4)的前三列,直接可用:
| 用例 | 因子A | 因子B | 因子C |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 2 |
| 3 | 1 | 3 | 3 |
| 4 | 2 | 1 | 2 |
| 5 | 2 | 2 | 3 |
| 6 | 2 | 3 | 1 |
| 7 | 3 | 1 | 3 |
| 8 | 3 | 2 | 1 |
| 9 | 3 | 3 | 2 |
你可以在任何一行任何一列上验证:任意两列的水平组合都出现且仅出现一次。这就是正交表的正交性。
实操中有个坑要提醒:因子和水平必须先做筛选。我见过有人把内存大小、屏幕分辨率、操作系统、浏览器、语言、时区全都塞进正交表,结果九个用例每个都要重新装环境,执行成本远超收益。正确做法是先做一轮风险筛选,只保留"历史上出过问题"和"本次版本改动涉及"的因子。通常三到四个因子就够了,水平数控制在三以内。
4.2 场景法:基本流、备选流、异常流
场景法是把系统当成一个用户旅程来看,按事件流组织用例。标准做法是先定义基本流(一切顺利的主干路径),再从中分岔出备选流(用户选了另一条合法路径)和异常流(出错、超时、中断、回退)。
这三个流的关系可以这么理解:基本流是高速公路,备选流是匝道,异常流是事故和封路。只测基本流的测试,相当于只在天气最好的白天开过一次车,然后就宣布这条路没问题。
场景法最容易漏的不是异常流,而是流的组合。比如"支付超时后用户返回订单页重新支付"这条路径,它跨了支付模块和订单模块,既不是纯粹的基本流也不是纯粹的异常流,是两者的组合。这类路径在真实用户里出现频率很高,但用例设计时极容易被忽略,因为写用例的人脑子里是按模块分的,不是按用户旅程分的。
4.3 案例分析:下单支付全流程
基本流:进入商品页 → 加入购物车 → 结算 → 选择地址 → 选择支付方式 → 支付成功 → 订单状态变更为已支付。
从这个主干分岔出来的场景:
| 场景编号 | 场景类型 | 关键路径 | 断言重点 |
|---|---|---|---|
| S-01 | 基本流 | 全流程顺利支付 | 订单状态、库存扣减、支付流水 |
| S-02 | 备选流 | 结算时修改地址 | 运费重算、地址快照 |
| S-03 | 备选流 | 使用优惠券后支付 | 实付金额、优惠明细 |
| S-04 | 异常流 | 支付超时未支付 | 订单进入待支付超时,库存回滚 |
| S-05 | 异常流 | 支付成功但回调延迟 | 订单最终一致性 |
| S-06 | 异常流 | 支付中用户手动返回 | 订单不重复、不脏写 |
| S-07 | 组合流 | 支付失败后重新支付 | 不重复扣款、库存正确 |
| S-08 | 组合流 | 支付成功后申请退款 | 退款金额、库存是否回滚 |
S-05 和 S-07 是重灾区。S-05 涉及异步回调,本地订单状态和支付平台状态可能短时间不一致,如果前端没有做轮询或者轮询间隔太长,用户会看到"已支付但订单显示待支付",然后第二次点击支付。这就串到了 S-07——重复支付。这两个场景必须一起设计、一起断言,单独测其中一个都发现不了完整问题。
注意:异常流用例一定要明确写出"预期恢复行为"。例如支付超时后库存回滚是立即回滚还是延迟 30 分钟回滚,这个策略不同,断言的时间点就不同。写用例时不写清楚,执行的人只能凭感觉等,结果就是时好时坏的偶发失败。
5. 错误推测法与状态迁移法:经验兜底与状态补位
5.1 错误推测法怎么做得有据可依
错误推测法经常被误解成"凭感觉瞎猜",这其实是把它用废了。真正有效的错误推测是有输入来源的,我一般从四个地方取材:
第一是历史缺陷库。把过去三个版本该模块的 bug 单拉出来,按类型归类,看哪些是重复出现的。比如某模块连续三个版本都出过"并发下单库存超卖",那这一条就必须进用例集,而且优先级最高。
第二是开发实现方式。用例评审时问一句"这块是怎么实现的",往往能问出关键信息。比如开发说"用了本地缓存做去重",那你立刻要想到缓存过期、缓存与数据库不一致、多实例部署缓存不共享这三个场景。
第三是变更影响面。本次版本改了哪些文件、哪些接口、哪些配置项,改动点周边的逻辑就是风险高发区。这类推测不需要经验,只需要看 diff。
第四是同类系统的公开经验。支付、库存、优惠计算这些通用场景,行业里踩过的坑高度相似,直接拿过来做检查清单,比自己从头想效率高得多。
这四个来源里,第二条的性价比最高,也最容易被忽略。很多测试同学拿到需求就直接写用例,根本不和开发聊实现,结果测的全是需求文档表面上的东西。
5.2 状态迁移法:把系统当成一台状态机
状态迁移法适用于有明确状态流转的对象,比如订单、工单、审批单、账号。做法分三步:先列出所有状态,再列出所有合法的迁移路径,最后补上所有非法迁移路径。
第三步是关键,也是绝大多数人跳过的一步。非法迁移指的是"从当前状态直接跳到某个不该到达的状态",比如订单从"待支付"直接跳到"已完成",或者已取消的订单再次发起退款。这类操作可以通过接口直接调用、修改 URL 参数、重放请求等方式触发,正常页面操作走不到,但攻击者和异常客户端能走到。
我常用的做法是画一张状态迁移表,行是起始状态,列是目标状态,格子填"合法/非法/不适用"。这张表填完,用例数量自然就出来了,而且不会有遗漏。
5.3 案例分析:订单状态机与非法迁移
以订单为例,状态包括:待支付、已支付、待发货、已发货、已完成、已取消、退款中、已退款。
| 起始状态 | 目标状态 | 合法性 | 用例要点 |
|---|---|---|---|
| 待支付 | 已支付 | 合法 | 正常支付 |
| 待支付 | 已取消 | 合法 | 用户取消、超时取消 |
| 已支付 | 待发货 | 合法 | 系统自动流转 |
| 待发货 | 退款中 | 合法 | 发货前退款 |
| 已发货 | 已完成 | 合法 | 确认收货、超时自动完成 |
| 已完成 | 退款中 | 合法 | 售后退款 |
| 待支付 | 已完成 | 非法 | 接口直调应拒绝 |
| 已取消 | 已支付 | 非法 | 需验证状态校验 |
| 已退款 | 退款中 | 非法 | 需验证防止重复退款 |
| 已取消 | 已发货 | 非法 | 需验证发货状态校验 |
"已取消 → 已支付"这条非法迁移,我在两个项目里都真的抓到过问题。原因是支付回调接口只校验了订单号存在,没有校验订单当前状态,用户在支付页停留很久,订单因为超时被取消了,用户这时候付款,回调进来直接把状态改成已支付,然后系统给一个已经释放了库存的订单发货,直接造成超卖。这类 bug 靠页面点击是永远测不出来的,必须用状态迁移表加接口直调才能覆盖。
6. 从方法到用例库:落地执行的几个关键动作
6.1 用例模板字段怎么设计
方法学得再好,落到表格里字段设计不对,一样会乱。我在团队里推的模板必填字段是这几个:
- 用例编号:模块前缀 + 三段式,比如
ORDER-PAY-001,方便按模块筛选和关联自动化脚本。 - 设计方法:标注这条用例是用哪个方法设计的。这个字段看着虚,作用很大——评审时一眼能看出方法分布是否失衡,如果整个模块全是"错误推测",说明前面的方法都没用上。
- 前置条件:必须写成可复现的状态,比如"账号 A 有 1000 元余额、账户状态正常",而不是"用户已登录"。含糊的前置条件会让用例在不同人手里跑出不同结果。
- 操作步骤:一步一个动作,不要合并。"输入金额并点击提交"要拆成两步,因为这两步可能分别在两个端做校验。
- 预期结果:分层写,UI 层、接口层、数据层各写一句。只写 UI 层的用例,测不出数据层的问题。
- 优先级:P0 到 P3。P0 是冒烟必跑,P1 是回归必跑,P2 按需,P3 是低频边界。
字段之外还有一条规矩:一条用例只验证一个点。把三个断言塞进一条用例里,失败的时候你根本不知道是哪一个先崩的,排查成本翻倍。
6.2 用例数量与优先级的控制
用例数量没有一个绝对标准,但有一段经验区间。一个中等复杂度的业务模块,按七个方法走完,通常能产出 80 到 150 条,其中 P0 不应该超过 15 条。如果 P0 有 50 条,说明优先级定义失效了——什么都重要等于什么都不重要。
控制数量的两个手段:一是合并同参数不同数据的用例,用参数化方式表达,比如十五条金额用例可以合并成一条参数化用例加一张数据表;二是裁剪重叠用例,定期做一轮用例盘点,把连续三个版本都没失败过的 P3 降级或者归档。
这里我要说一句可能不太讨喜的话:用例条数不是工作量证明。我见过有人为了显示自己测得全面,把一个字段写了四十条用例,结果真正的业务规则覆盖漏洞一个没补。评审的时候我只看两个数字——方法分布的均衡度和 P0 用例对核心业务的覆盖比例。
6.3 评审、维护与自动化衔接
用例评审最怕开成朗读会。我的做法是评审前把用例发给产品和开发,评审时只讨论三类条目:标注了"设计方法为判定表/状态迁移"的规则型用例、涉及金额和状态变更的高风险用例、以及所有 P0 用例。其他条目快速过。
维护上,我习惯在每次版本上线后做一次用例回流:把线上发现的 bug 反向补成用例,标注来源是"线上缺陷",并把它放到对应的方法分组下。有个规律很有意思——线上 bug 补出来的用例,超过七成属于场景法的异常流和状态迁移法的非法迁移,这两类恰恰是手工写用例时最容易被跳过的地方。
自动化衔接方面,参数化程度高的用例(等价类、边界值、正交表)最适合先自动化,因为这些用例结构统一、断言明确、数据可外部化。判定表和状态迁移的用例自动化成本相对高一些,因为它们往往跨多个接口和状态。我的顺序是先自动化边界值和正交用例做冒烟,再逐步把场景法的主干流做成端到端脚本。
# 边界值用例参数化的写法,数据外置方便维护和复用 import pytest CASES = [ ("TC-02", "0.01", "pass", "下限有效"), ("TC-04", "0", "fail", "下限外"), ("TC-05", "-1", "fail", "负数"), ("TC-06", "50000.00", "fail", "上限命中"), ("TC-07", "50000.01", "fail", "上限外"), ("TC-08", "0.001", "fail", "精度超限"), ("TC-09", "0.005", "depends", "舍入边界,需确认策略"), ] @pytest.mark.parametrize("case_id, amount, expect, note", CASES) def test_withdraw_amount_boundary(case_id, amount, expect, note): result = withdraw_api.submit(amount=amount) assert result.status == expect, f"{case_id} {note} failed"这段代码里我最想强调的不是写法,而是最后那条TC-09的depends状态。遇到行为不确定的用例,不要假装它确定了,把它标出来,等和开发确认策略后再补断言。冒充确定的结果,就是在给未来埋雷。
7. 常见问题排查与避坑实录
7.1 问题速查表
把团队里高频出现的几类问题整理成表,遇到时直接对号入座:
| 现象 | 常见根因 | 排查方向 | 处理建议 |
|---|---|---|---|
| 用例数量很多但漏测明显 | 方法单一,全是需求翻译 | 统计用例的"设计方法"字段分布 | 强制补充判定表和场景法用例 |
| 同一功能多条用例结果不一致 | 前置条件描述含糊 | 检查前置条件是否可复现 | 前置条件写死具体数据 |
| 边界用例全部通过但线上出问题 | 只测了数值边界,没测精度和类型 | 补精度边界、类型边界 | 增加半精度点和格式无效类 |
| 组合场景 bug 反复出现 | 只做单条件测试 | 检查是否有判定表用例 | 用判定表补组合规则 |
| 回归时间越来越长 | P0 定义失效,用例只增不减 | 统计 P0 占比和失败率 | 设 P0 上限,定期归档低价值用例 |
| 偶发失败查不出原因 | 断言只到 UI 层 | 检查是否断言了接口和数据层 | 分层断言,补充日志关联 |
| 接口直调能绕过状态校验 | 未测非法迁移 | 用状态迁移表逐格核对 | 补非法迁移用例并推动加校验 |
7.2 几条用血换来的经验
第一条,先和开发聊十分钟,胜过自己闷头写两小时。前面提过好几次,这里再强调一遍。很多分支逻辑需求文档里根本不写,比如"金额为空时默认按 0 处理"还是"直接报错",这种差异决定了你的用例断言完全不同。花十分钟问清楚实现方式,能省下大量返工。
第二条,不要相信"这个字段前端已经限制了"。前端限制只能拦住普通用户,接口直调、抓包改包、模拟器调试都能绕过去。所有关键校验都必须有一条跳过前端直接调接口的用例。我在实际项目里抓到的最严重的一个越权问题,就是通过直接调用接口传了一个页面上根本选不到的参数值触发的。
第三条,异常流的恢复行为要写清楚,不能只写"报错"。"支付超时"这条用例,如果只写预期结果是"提示支付超时",那执行的人测完就走了,根本不会去看库存有没有回滚、优惠券有没有退回、订单有没有进入可重试状态。异常流的价值恰恰在这些后续动作上。
第四条,用例的命名比用例的步骤更重要。一条用例叫"测试提现功能",三个月后没人知道它测了什么;叫"提现金额 0.005 应按既定舍入策略处理后落库",任何人一看就懂。命名这件事花不了多少时间,但决定了用例库能不能长期维护下去。
第五条,把"设计方法"当成一种自我检查。每次写完一个模块的用例,我会看一眼方法分布。如果发现全是等价类和边界值,说明这个模块的业务逻辑我还没吃透,规则型用例一条都没设计出来。这个检查动作很简单,但每次都能让我回去补上一批判定表和场景法用例。
第六条,接受覆盖不完备这件事,但要让不完备是"已知的"。没有任何一套用例能覆盖所有路径,这是事实。但"我知道这里没覆盖,因为风险低、成本高,所以我主动放弃了"和"我不知道这里没覆盖"是两回事。前者可以写进测试报告,后者是线上事故的伏笔。所以我的习惯是在用例集最后留一节"本次未覆盖项及原因说明",把边界外的情况、低概率的组合、依赖外部环境的场景明确列出来,评审时大家一起确认。
这套方法我用了几年,从最开始只会写等价类,到后来慢慢把判定表和状态迁移用熟,中间踩的坑大多集中在"用错工具"和"跳过异常流"这两件事上。真正让用例质量提升的,往往不是多学一个方法,而是把手上这几个方法用对位置——单字段的用等价类和边界值打磨干净,多条件的用判定表钉死规则,跨模块的用场景法和状态迁移串起来,最后拿错误推测法扫一遍边角。等你哪天写完一个模块的用例,能一眼看出哪些条是用哪个方法设计出来的时候,这套东西基本就算长在脑子里了。