AI 代理同时生成代码和验证代码,两者携带相同的误解,导致测试全部通过但实际存在边界条件 bug。覆盖率指标只衡量代码行是否执行,不衡量断言是否捕捉了错误行为。
测试套件在四十二秒内全部通过,这反而让随后发生的生产事故更加令人难堪。我们的 coding agent 在重构患者登记流程时同步生成了一套单元测试,合并 pull request 时所有断言都是绿色的。两天后,一份合规报告发现一名 120 岁的患者已被登记,违反了本应拒绝该年龄及以上人员的政策。根本原因追溯到一个本应被测试捕获的边界条件,而最糟糕的是:测试并非缺失,而只是断言了错误的边界。
这种共同的假设就是 agent 生成测试的标志性弱点——因为模型将一种心智模型同时编码到了代码及其验证中。实现中的一个误解会被忠实地复刻到测试中,产生虚假的绿色:套件通过了,覆盖率报告看起来也很健康。Bug 之所以能蒙混过关,是因为测试将 buggy 行为断言为了规范,而覆盖率百分比只测量了哪些行被执行了,并不测量断言是否能察觉某行已损坏。
Mutation testing 填补了这个空白,它提出了一个不同的问题——测量测试能在多大程度上区分代码与其损坏版本。工具向源代码中引入小的缺陷(即 mutations),然后针对每个变体运行测试套件;如果变体存在时测试仍然通过,则该 mutation 存活了下来,而这种存活本身就是测试盲区的直接信号。具有高 mutation 存活率的测试套件不仅仅是 incomplete 的,它在actively误导,因为它在 broken code 仍未被检测到的情况下报告绿色。这使得 mutation 存活率成为一个比覆盖率更诚实的指标,用于评估 agent 生成的测试。
我使用开源 MonkeyCode 项目当前的免费额度在这个登记验证器上运行了这个工作流,包含 1000 万 token 和一个免费服务器选项。披露:本文是 MonkeyCode 产品推广的一部分。搭建大约花了二十分钟,除了一个 SSH 客户端外无需任何本地依赖,因为免费服务器提供了一个隔离的 Linux 环境,我可以在其中安装 mutation 工具而无需触碰笔记本上的 Python 安装。Token 额度覆盖了整个会话,包括一次对话——我让模型解释每个存活的 mutation 并将其分类为真正的 gap 或误报。
被测试的模块是一个小小的年龄验证器,解析字符串、拒绝负值、拒绝过大数值:
# validator.py
def validate_age(age_str):
age = int(age_str)
if age < 0:
raise ValueError("Age cannot be negative")
if age > 120:
raise ValueError("Age cannot exceed 120")
return age
Agent 生成了三个测试:一个针对有效输入,一个针对负输入,一个针对超过上限的输入。套件通过了,覆盖率报告为百分之百,这让 mutation 结果更加有趣。工具引入了一个 mutation,将 age < 0 改为 age <= 0,而套件仍然通过了,因为没有任何测试覆盖到零这个边界值。另一个 mutation 将 age > 120 改为 age >= 120,套件再次保持绿色,因为上限测试使用的是 121 而非 120 本身。这个小小模块的存活率大约为百分之三十,每个存活的 mutation 都可以追溯到一个边界条件。
修复方案不是更长的测试文件,而是更精准的测试策略,我添加了两个边界测试,一个针对零,一个针对 120,将这个模块的 mutation 存活率降到了零。有趣的是,agent 最初的测试并非偷懒,只是被同样塑造了实现方案的边界盲区所塑造。模型在代码中写了 age < 0,然后在测试中针对 -1 和 121 编写测试——这些是使比较为真的值,而非探测比较本身的值。Mutation testing 在几分钟内暴露了这种模式,而 code review 可能完全错过它。
同样的工作流在做一些实际调整后可以扩展到更大的代码库,第一个是计算成本,因为 mutation testing 每次变体都要运行完整套件。一个大型项目在一台普通机器上可能需要数小时,所以免费服务器处理这个小型模块绰绰有余,但会力不从心于正经代码库。免费额度的 token 对于交互式会话来说是慷慨的,但它并不能替代专用的测试基础设施,团队应将其视为实验沙盒而非永久流水线。Mutation 工具也会产生误报,即存活的 mutation 反映的是等效代码而非真正的 gap,所以结果需要人工解读而非盲目自动化。
可复用的经验是:agent 生成的测试理应得到与 agent 生成代码同等的怀疑,而 mutation 存活率是这种怀疑的一个具体指标。来自 agent 的绿色套件是一个起点,而非结论,我在这里发现的边界盲区模式足够常见,值得对任何关键模块快速运行一次 mutation。如果你想要复现这个工作流,MonkeyCode 免费额度是一个合理的实验沙盒,尽管该技术本身是工具无关的,能与你已有的任何 mutation 框架配合使用。
MonkeyCode 提供可以运行此工作流的免费模型。