“按需求写用例"是新人第一直觉,也是天花板最低的方法——需求文档写得越细,用例越"正确”,而 bug 恰恰藏在需求没写的地方。这篇讲用例设计的三个层次:正确性、完整性、风险优先级,以及怎么从"翻译需求"进化到"驱动风险"。
一、第一层:正确性(把需求翻译对)
最基础但最常被低估:用例先验证"需求本身",再验证"实现"。
1. 需求歧义先行
写用例前过一遍需求,把所有"看起来理所当然"的假设显式化:
1 | 需求:"用户输入手机号注册,手机号已存在时提示'该手机号已注册'" |
经验:歧义清单里 70% 的问题,开发自己也没想过——用例设计在这个阶段就是在帮开发补需求,越早暴露成本越低。
2. 等价类 + 边界的标准动作
1 | 输入:手机号(11 位数字) |
等价类不是"列一堆",是每类只取代表值——11 位手机号不需要测 100 个,取号段代表 + 边界即可。用例数量失控的根源是"穷举",等价类的本质是"有依据的抽样"。
二、第二层:完整性(覆盖需求之外的面)
需求没写的地方按四个通用维度补:
1. 状态维度
"提交订单"是动作,但订单有状态机。用例要覆盖状态迁移而不是孤立动作:
1 | 状态机:草稿 → 已提交 → 已支付 → 已发货 → 已完成 |
2. 时序维度
并发和乱序是需求永远不写的:
- 并发:两个客户端同时改同一订单 → 谁赢?冲突提示?
- 乱序:支付回调晚于"取消"到达 → 钱扣了单没了?
- 超时:请求发了没响应,用户又点了一次 → 双单?
- 重试:客户端超时重发,服务端已处理 → 幂等吗?
这四个问题每个功能都值得问一遍,问不出答案的需求,测试阶段就是排雷现场。
3. 数据维度
- 空数据:新账号 0 订单、0 消息的页面表现;
- 极限数据:单条超长文案(1000 字符备注)、单页 10 万条列表;
- 脏数据:历史迁移来的异常值(负数金额、乱码昵称)。
4. 环境维度
(详见弱网/安全两篇)弱网、断网恢复、多语言、深浅色、不同分辨率、老版本兼容。
三、第三层:风险驱动(把有限用例投给最可能出事的地方)
用例永远不够用,排优先级 = 按风险排布:
1 | 风险值 = 失效概率 × 失效影响 |
落地成用例标记 + CI 分档:
1 | # 每个用例标风险级(标记进用例元数据) |
风险驱动的检验标准:一次线上事故后回看,触发事故的路径是否在 @critical/@high 里。如果事故路径跑的是 @medium 而没跑 @critical 的用例——不是漏用例,是风险评错了,更新评级模型比补一条用例重要。
四、用例资产化:设计方法的载体
设计方法再好,落在"脑子里"就等于零。载体要求:
- 用例结构化(标题/前置/步骤/预期/风险级/关联需求),平台可检索可统计;
- 需求 ↔ 用例双向可追:需求改动能查影响用例(反向 blast radius),用例能查覆盖哪条需求;
- 评审留痕:风险评级和"不测某路径的理由"都写进用例——"为什么没测并发"在事故复盘时是要交代的;
- 定期重评:风险级不是定死的,每季度按 bug 数据重排一次。
五、小结
用例设计的三层递进:正确性(假设显式化 + 等价类抽样)、完整性(状态/时序/数据/环境四维度)、风险驱动(失效概率 × 影响,用例投给最可能出事处)。从"需求翻译"到"风险驱动"的分水岭是一个问题的转变:翻译问"需求说了什么",风险驱动问"哪里会出事,出了事多疼"。