
事情是这样的。
前段时间有个测试负责人找我,说他团队花了两个月,写了 3000 多条 case,代码覆盖率干到 95%。老板看了很满意,团队也觉得稳了。
上线第二天,炸了。
线上冒出来一个极其严重的 bug,3000 条 case 全没拦住。不是什么极限边界场景,就是一个很普通的操作路径,测试团队压根没想到。
3000 条 case,没一条踩中。
他特别郁闷。该做的都做了,case 数量够多,覆盖率够高,但真到线上,该漏的还是漏了。
我听完只有一句话,你测了很多,但你没测到点子上。
这事太常见了。
我见过太多团队,把测试质量等同于测试数量。case 写了几千条,覆盖率拉到 90% 以上,就觉得心里有底了。
但覆盖率这个东西,测的是你的代码被跑过了没有,不测你的代码跑得对不对。
你写 100 条 case 把每一行代码都跑一遍,覆盖率 100%。但如果这 100 条 case 全是最理想的输入、最顺畅的路径,那你只是反复确认了一件事,正常情况下代码能跑。仅此而已。
真正翻车的地方,异常输入、并发冲突、边界条件、模块集成,一条没测。
覆盖率是个好指标,但别把它神化了
先说清楚,我不是否定覆盖率。
覆盖率是个有用的参考。它告诉你哪些代码完全没被测过,这是有价值的信号。一个核心模块覆盖率只有 20%,那确实该补。
但太多团队把覆盖率当成了目标,而不是参考。
经济学里有个古德哈特定律。简单来说就是,当一个指标变成目标,它就不再是个好指标了。
放到测试上一模一样。
你一旦把覆盖率设成 KPI,团队行为就会变形。为了把覆盖率从 85% 撑到 90%,开始大量写那种跑一下就过、没有实质断言的 case。一个测试函数调了一下目标函数,不检查返回值,不检查副作用,覆盖率贡献了,测试价值约等于零。
数字好看了,质量没变。
INRIA 有过一项大规模研究,分析了大量开源项目,结论很直接,在项目级别,覆盖率和发布后发现的 bug 数量之间,相关性微弱到可以忽略。
你没看错。覆盖率高的项目,不一定 bug 少。
还有一种更隐蔽的问题。覆盖率不区分代码的重要性。你把一个日志工具类测到 100%,和一个核心支付逻辑测到 100%,在覆盖率报告里权重是一样的。但前者出 bug 最多日志格式错一下,后者出 bug 就是资金事故。
盯着覆盖率干活,大量精力会被低风险代码占满,真正高风险的地方反而没时间好好测。
测得多,不如测得准
那什么才叫好的测试。
一句话,测该测的地方。
好的测试不是面面俱到,是精准命中高风险区域。你不需要测每一行代码,你需要测的是一旦出问题、后果最严重的那些路径。
这在测试方法论里叫风险驱动测试。思路很简单,按「出 bug 的概率」乘以「出 bug 的后果」排优先级。概率高、后果严重的,重点测。概率低、后果也轻的,少花时间甚至不测。
举个具体例子。
电商系统里,支付链路和日志清理脚本能一样对待吗。支付链路出 bug,用户付了钱没收到货,客诉、退款、信任崩塌。
日志清理脚本出 bug,最多磁盘满了告个警。前者你应该花大量精力写边界测试、异常测试、并发测试,后者跑个基本 smoke test 就够了。
但如果你盯着覆盖率干,这两块代码得到的关注可能是一样的。
这就是问题所在。
测试资源永远是有限的。你不可能什么都测,你需要做的是把有限的资源砸到最值钱的地方。
怎么找到该测的地方
道理讲完了,实操的问题来了。怎么知道哪些地方该测。
我自己的经验,按这几条线索去找。
第一,盯真实故障。
线上真正出过的 bug,是最高质量的测试需求来源。每一次线上事故,都应该沉淀出测试 case。不是泛泛地说「以后注意」,是实打实写一条 case,锁死这个场景,确保同样的 bug 不会再出现。
很多团队的事故复盘写得很好,改进措施列了一堆,但就是不落地成自动化测试。过两个月换个姿势,同一个坑再踩一次。
真实故障还有一个好处。它告诉你你以为安全的地方其实不安全。你测得最多的是核心功能,但线上炸的往往是那种你觉得「这么简单不可能出问题」的角落。真实数据帮你打脸,打完你就知道该补哪了。
第二,盯改动频繁的地方。
代码变更频率是个很好的风险信号。一个模块三个月改了 20 次,要么需求在反复变,要么这块逻辑本身就复杂不稳定。不管哪种,都是 bug 高发区。
每次改完代码手动跑一遍觉得没问题就提交,迟早翻车。改动频繁的模块,回归测试必须自动化,断言要细。
第三,盯集成点。
单个模块自己跑没问题,挂到系统里就出事。这种事天天都在发生。
服务之间的接口契约、数据格式传递、异步消息的时序、数据库事务的隔离级别,这些集成点的 bug 远比单元逻辑的 bug 多。但这些地方恰恰是很多人懒得测的,因为搭环境麻烦,造数据麻烦,跑起来慢。
嫌麻烦不测,上线了就轮到用户帮你测了。
第四,用变异测试验一把。
前面三条帮你找「该测哪些地方」,变异测试解决的是另一个问题,你写的那些测试到底有没有用。
原理很直白。在你的代码里主动注入一些 bug,比如把大于号改成小于号,把加号改成减号,删掉一行校验逻辑,然后跑你的测试套件。如果测试挂了,说明你的测试能抓到这个 bug,有效。如果测试全过了,说明你的测试根本没检查到这块逻辑,白写了。
这比看覆盖率诚实多了。覆盖率告诉你代码被跑过了,变异测试告诉你跑过了之后到底有没有真正检查。
主流的变异测试工具,Java 有 PIT,Python 有 mutmut,JS 有 Stryker。接进 CI 周期性跑一轮,你会惊讶地发现,覆盖率 90% 的测试套件,变异得分可能只有 40% 到 50%。说明一大半的测试在假装保护你。
AI Agent 场景,精准测试更关键
前面聊的主要是传统软件测试的思路。如果你做的是 AI Agent,这事更极端。
传统软件你好歹还能算覆盖率,输入空间虽然大但好歹有限。Agent 的输入空间是无穷的。用户可能问出任何问题,可能用任何顺序操作,可能给任何格式的数据。你想穷举,不可能。
更麻烦的是 Agent 的非确定性。同一个输入,今天输出 A,明天输出 B。你写了一条 case 跑过了,不代表稳定通过。跑 10 次可能 7 次过 3 次挂,你按一次的结果来判断,跟抛硬币没区别。
这种情况下,精准测试不是优化项,是生存项。
怎么选,跟前面一样的思路。
从真实用户对话里捞失败 case。用户骂得最多的问题类型,就是最该建评估器的场景。与其花一周合成 1000 条测试数据,不如从线上捞 50 条真实失败 case,后者价值大十倍。
盯核心业务链路。你的 Agent 如果是做客服的,退换货流程比闲聊场景重要一百倍。把退换货的全链路测透,比面面俱到地测一百个边缘场景管用。
跑多次看概率。一个 case 至少跑 5 到 10 次,看通过率。一次通过不叫通过,十次通过八次以上才叫靠谱。
几条实操经验
道理和方向都有了,最后给几条我自己踩坑后总结的。
第一,定期清理低价值测试。
测试不是越多越好。那些跑一下就过、断言空泛、从来没抓到过 bug 的 case,是负债不是资产。它们拉慢 CI、增加维护成本、给你虚假的安全感。定期 review,该删的删,该重构的重构。好的测试套件是精炼的,不是臃肿的。
第二,跟踪逃逸缺陷,别盯覆盖率。
逃逸缺陷就是线上发现的 bug。这个数字比覆盖率诚实得多。覆盖率 95% 但每个版本线上都冒 bug,你的测试体系是有问题的。覆盖率 70% 但线上几乎不出事,说明你那 70% 全砸在了刀刃上。
真正该盯的指标是,你测过的东西,还有多少跑到线上炸了。
第三,测试跟着业务风险走,不是跟着代码量走。
代码多的地方不一定该多测,风险高的地方才该多测。每次新功能上线前,先做风险评估。哪些路径涉及钱、涉及数据安全、涉及用户体验的关键节点,先把这几个地方测透了,再考虑其他的。
第四,Agent 评测,质量远比数量重要。
50 条从真实业务场景里提取的评估 case,比 500 条合成数据有用得多。一条能抓到核心问题的 case,胜过一百条跑 happy path 的废 case。评测的价值不在于你有多少条数据,在于你的数据能不能反映真实的业务质量。
写在最后
回到开头那个测试负责人。
3000 条 case,覆盖率 95%,听上去很厉害。但如果这 3000 条 case 全在测代码有没有被跑过,而没测到真正的风险点,那这 3000 条 case 就是一堆好看的数字。
测试的价值,从来不在于数量。在于精准。
你测了 100 个地方,不如测准 10 个最关键的。你写了 1000 条 case,不如有 100 条能真正抓住 bug。
好的测试,不是测得多。是测得准。
知道哪里该测,哪里可以不测,哪里必须往死里测。这种判断力,比写 case 的能力重要一百倍。