

我们的回归用例库,跑了五年,积累到 5000 条。每次全量回归,要跑六个多小时。
没有人敢删用例。每个人都怕删掉的那一条,恰好是某次事故复盘后加进去的"保命用例"。结果就是只增不减——新功能上线加用例,没有人会同步审查旧用例是否还有存在的必要。
直到一次容量评估,我们才认真算了一笔账:跑一次全量回归的机器成本和人力成本,已经高到影响了发布节奏。这时候才有动力去做一件一直拖着没做的事——找出哪些用例是冗余的。
但"找冗余"这件事,人工做几乎不可行。5000 条用例,两两比较判断是否覆盖同一逻辑,组合数是天文数字。这正是适合交给 AI 处理的场景:规则可以显式化,工作量巨大但单步判断不复杂。
动手之前,必须先把"冗余"定义清楚——这是整个项目最容易出错的一步,定义错了,AI 再精准也是错的方向。
我们最终确定了三类冗余,而不是笼统的"重复":
完全重复型:两条用例的输入、操作步骤、预期结果完全一致,只是用例标题或编号不同。这种最容易识别,也是历史上最常见的成因——不同人不知道已有用例,重复创建。我们这次发现的完全重复用例有 380 条,占总量的 7.6%,主要集中在团队人员流动较多的几个模块。
等价覆盖型:两条用例验证的是同一个等价类。比如"输入合法手机号 13800138000"和"输入合法手机号 13900139000",验证的是同一条业务规则(合法手机号格式校验),属于同一等价类,只保留一条即可。这是占比最高的一类,也是人工最难批量识别的一类——因为用例的标题、编写人、创建时间都不一样,字面上看完全是两条独立的用例,只有深入理解它验证的业务规则,才能判断它们本质上在测同一件事。
被包含型: A 用例的验证路径完全覆盖 B 用例的验证路径,且多验证了额外的步骤。比如"用户登录"和"用户登录后修改密码"——后者的执行路径包含了前者的全部步骤和断言。这种情况下,前者可以被后者替代,除非前者作为独立场景有快速冒烟的价值(比如登录用例本身要求在两分钟内跑完,作为部署后的健康检查)。
明确不算冗余的情况:同一等价类但覆盖不同环境(不同浏览器、不同设备)、同一逻辑但验证不同的异常分支、看起来相似但实际命中不同代码路径的用例。这些必须保留,我们专门写了一份"保留清单"规则,在 AI 分析阶段就主动排除这些场景,防止误判为冗余。
5000 条用例原始格式是 Excel 和 Markdown 混杂,字段不统一。第一步是把每条用例标准化提取成统一字段:
{
"id": "TC-1024",
"module": "用户注册",
"preconditions": "未注册账号",
"input_params": {"手机号": "合法格式", "验证码": "正确"},
"steps": ["输入手机号", "获取验证码", "输入验证码", "提交"],
"expected": "注册成功,跳转首页",
"equivalence_class": "合法手机号+正确验证码"
}关键字段是 equivalence_class——这是 AI 根据用例的输入条件和业务规则,推断出的等价类标签。这一步本质上是让 AI 读懂每条用例在测试什么"业务规则",而不只是字面上的"操作了什么"。
按 module + equivalence_class做分组聚类,同一组内的用例就是候选冗余集合。
这一步发现了一个意料之外的情况:用户注册模块里,"手机号格式校验"这一个等价类,历史上被不同的人在不同时间重复创建了 11 条用例,字段命名各不相同(有的叫"测试合法手机号",有的叫"verify valid phone number"),如果按用例标题做字符串匹配,根本发现不了它们是同一类。
按等价类聚类之后,这 11 条用例只需要保留 2 条——一条覆盖正常路径,一条覆盖边界值(手机号位数边界)。
AI 输出的是"建议合并"清单,附上判断依据:
建议:TC-1024, TC-1031, TC-1048 ... (共11条) 可合并为 2 条
判断依据:同属"用户注册-手机号格式校验"等价类
保留建议:TC-1024(正常路径) + TC-1039(11位边界值)
待确认:TC-1045 涉及国际手机号格式,是否为独立等价类?这一步刻意设计成"AI 不能自主删除"——每一条合并建议,必须由测试负责人确认。这不是流程冗余,是必要的风险控制:AI 的等价类判断基于显式规则,遇到业务里隐藏的特殊场景(比如那条"国际手机号格式"),它会标记"待确认"而不是自行决定,但这依赖人在裁决环节真的认真看,而不是无脑批量通过。
为了让裁决环节真正有效,我们要求负责人在确认每一组合并建议时,必须在系统里手动填写"为什么这条可以合并"或"为什么需要保留",而不是简单点一个"同意"按钮。这个小小的摩擦设计,显著降低了"为了赶进度随手全部通过"的风险——事后抽查发现,有 7% 的 AI 建议在这个环节被驳回,理由大多是"这条用例虽然等价类相同,但创建时附带了一个已知 Bug 的回归标记,不能删"。这类信息,只存在于资深工程师的记忆里,AI 的分析无法触及。
光是"压缩数量"没有意义,核心约束是覆盖率不能下降。验证分两层。
第一层:需求覆盖率比对。把压缩前后的用例集,分别和需求点清单做映射,确认每一个需求点依然至少有一条用例覆盖。这一步是硬性门槛,任何需求点出现"裸奔"(无用例覆盖),整个合并方案打回重做。
第二层:代码覆盖率比对。这是更精确的验证——分别跑压缩前的 5000 条和压缩后的 3200 条,采集代码行覆盖率和分支覆盖率,逐文件比对差异。如果某个文件的覆盖率出现下降,说明被删除的用例里,有一条命中了一条独特的代码路径,需要把它找回来重新纳入保留清单。
这次压缩,最终代码行覆盖率从压缩前的 78.3% 变为压缩后的 78.1%,在可接受的误差范围内,我们额外补充了 6 条用例覆盖那 0.2% 的差异路径,最终做到完全持平。
这个验证过程也暴露了一个此前没意识到的问题:有 14 条被标记为"等价覆盖型可合并"的用例,实际代码覆盖率比对后发现,它们虽然输入条件属于同一等价类,但因为历史上的代码实现存在分支判断的细微差异,实际命中了不同的代码路径。这提醒我们,等价类的判断不能只看业务输入条件,还需要结合代码覆盖率做交叉验证——单靠业务层面的等价类分析,存在遗漏代码路径差异的风险。这 14 条最终被移出合并清单,保留下来。
5000 条压缩到 3200 条,精简 36%。回归测试时间从六个多小时降到三个半小时。
意外收获是,这次梳理顺带发现了 40 多条"僵尸用例"——验证的功能在历史版本里已经下线,但用例从来没人删除,一直在跑,一直返回 PASS,一直没人质疑过它的存在意义。
更意外的是,我们把"等价类判断逻辑"和"保留清单规则"写成了一份 Skill 文档——以后新增用例时,先过一遍这个 Skill 的检查,在创建阶段就能识别"这条用例和已有的某条属于同一等价类",从源头减少未来的冗余积累,而不是每隔几年再做一次大扫除。
测试用例越跑越多,几乎是每个测试团队都会走上的老路。但数量上去了,不代表覆盖就全了,很多时候不过是在“重复造轮子”,测来测去都是那点东西。
AI在这件事上真正帮上忙的,不是替人拍板做决定,而是把人根本做不来的大规模等价类分析变成了现实——像5000条用例之间两两比对这种活儿,人工根本不可能干完,但AI可以系统性地跑一遍。至于最后哪条该删、哪条该留,决定权还是牢牢握在测试人员自己手里。
这其实也给我们提了个醒:测试资产的价值,从来不在数量多少,而在覆盖率背后,那张干干净净、经得起推敲、没有丁点冗余的知识地图。