测试生成智能体:测试跑通了,什么也证明不了。
让智能体读你的代码、为它写测试,你会得到一套第一次运行就全绿的测试——而这正是问题所在:它是从实现里反推出规格的,所以实现错在哪里,测试就在哪里把这个 bug 认证为正确行为,并挡住修复。覆盖率发现不了这件事,批量评审也发现不了。唯一能在生成式测试面前站得住的验收标准是"证伪":一条测试要靠"在你故意改坏的代码上失败"来赢得它的位置。
判据难题:生成的测试会继承你的 bug。
一条测试有两半:驱动代码的准备部分,以及说明"正确答案是什么"的判据。智能体非常擅长前一半,在后一半上却存在结构性障碍——因为仓库里唯一精确描述"预期行为"的产物就是实现本身,而实现恰恰是被测对象。
- 读到什么代码,就编码什么代码。假设
discount(order)在总额为负时返回 0,而它本该抛出异常;从源码出发的智能体会写下assert discount(bad) == 0。这条测试正确描述了函数"做了什么",却写错了它"该做什么",并从此横在你和修复之间。下一个想改正这个函数的工程师会看到一片红——然后(这种事发生的频率远高于人们愿意承认的)他改的是测试。 - 这不是靠模型变强就能消掉的能力缺口。一段从未陈述意图的行为描述,无论读者多强,也无法从中还原出意图。修复要发生在上游:给智能体一个不是代码的判据——一段说明契约的 docstring、一个带真实约束的类型签名、一条写明期望输出的 issue、一份参考实现、一条必须成立的性质。
- 没有断言的测试反而是诚实的失败。一条只调用函数、只检查"没有抛异常"的生成测试至少没有撒谎。它同时也几乎一文不值,并且会把你所有的覆盖率数字往上顶。用 grep 扫一遍新测试里"零断言"或"只有一个恒真断言"的用例;那个数量通常比预期的高。
- Mock 会让情况更糟。智能体跑不通某个依赖时就会去 mock 它,而一条协作者全被 mock 掉的测试,断言的只是"你的 mock 按你交代的方式行事"。它会永远通过——包括在你把真实集成删掉之后。
一句话重构这个问题:生成一条测试,就是生成一个关于"预期行为"的主张,写码循环的其余每一条性质都是从这里推出来的。这与幻觉是同一个接地问题——输出流畅、可信,却除了被抄袭的那个对象之外没有任何真值来源作锚。
覆盖率是错的闸门,变异测试才是对的。
行覆盖率度量的是"测试跑的时候哪些行被执行了"。它不度量"哪些行为被钉住了",而一整套零断言的测试可以在什么都没钉住的情况下摸到 90%。给智能体一个覆盖率目标,它就会达成——那是一个形状良好的优化目标,也是一个形状糟糕的代理指标。
- 把闸门设成"必须失败",而不是"必须通过"。变异测试会扰动源码——翻转一个比较、改掉一个常量、删掉一条语句——然后重跑测试。存活下来的变异体就是一条没人测的行为。该写进 PR 的数字是变异得分,因为一条连"条件被反转"都察觉不到的生成测试是可度量地无价值的,而覆盖率永远不会告诉你这一点。
- 你不需要跑全量变异测试才能拿到好处。在大仓库上做变异测试很贵,但你要测的不是仓库,是这次的 diff。只对新测试声称覆盖的那些行做变异,整轮就压缩到几秒钟——便宜到足以作为阻塞闸门放进 CI。
- 廉价版同样管用。合入一套生成测试之前,把被测函数换成一个故意改坏的版本——提前
return None、把边界条件反过来——确认测试确实变红。一个脚本化的检查就能抓出大部分"什么也不断言"的测试。如果它依然全绿,那些测试就是装饰品。 - 永远不要让智能体看见闸门的内部。如果变异测试工具是智能体能调用、能反复迭代的工具,它会去过拟合那些变异体而不是过拟合行为,这就是多绕了几步的奖励攻击。闸门要在生成之后、针对成品运行,并且只回报"过"或"不过"。
真正划算的场景:判据早就存在的地方。
判据难题并不普遍——它只针对"写新规格"这件事。有三类工作的正确答案本来就摆在代码之外,在这些场景上,测试生成智能体是真正出色,而不只是产量高。
- 重构前的特征化测试(characterization test)。这里你要的不是测试"正确",而是测试忠实——一张精确捕捉代码今日行为的网,好让重写有东西可比。"实现即判据"在这里恰恰就是目的,bug 是被刻意钉住的,产量本身就是美德。这是这项技术回报最高的用法,也是大规模迁移得以启动的那一步。
- 从缺陷报告生成的复现测试。报告里就带着判据:这个输入、那个错误输出、这个期望输出。把它变成一条失败的测试是机械的、可核验的(修复前必须失败、修复后必须通过),而这正是让调试智能体变得可评分的那件交付物。
- 从既定契约生成的性质与不变量测试。"序列化再反序列化得到相等的对象"、"结果永远有序"、"输出长度不超过输入长度"——性质是可以直接告诉智能体、而不必让它去反推的规格,而一条性质测试探索的输入空间远大于人手会写的那几个用例。先要性质、后要用例;这个顺序比听上去更重要。
- 围绕既有断言做边界枚举。给定一条编码了意图的人写测试,智能体在向外扩展边界这件事上很可靠——空值、null、unicode、差一、最大值、负零。判据是从种子测试继承来的,而这恰恰是"反推"安全的那种情况。
把上面这份清单当选择规则再读一遍:只在你能用一句话说清判据是什么的地方派发测试生成任务。"当前行为"、"这份缺陷报告"、"这条不变量"、"这条种子测试"。如果说清它需要一整段话,那你是在要求智能体发明一份规格,而你接下来评审的将是伪装成代码的散文。
每一条生成的测试都是维护账本上的永久负债。
生成成本已经趋近于零。执行成本、评审成本,以及"因为错误原因而失败的测试"的成本,都没有。一套测试不是会累积的资产,而是一份订阅——每一次 CI、每一次未来重构都要结账,而买单的人并没有下过这个订单。
- 主要缺陷是过度指定,而不是不正确。智能体会对一切拿得到的东西做断言:精确的日志字符串、字典顺序、私有属性、错误信息的确切措辞。每一条都是一根会被无害改动触发的绊线。这样一套测试抓不到 bug,它抓的是"编辑动作"——并且会训练团队不再相信红色构建。
- 大批量生成的测试会让不稳定性叠加。一条失败率 0.5% 的测试只是烦人;四百条就让绿色构建变得不可能,也让后续每一次智能体运行不可用——因为后台编码智能体分不清"你的 flake"和"它自己的回归"。第二次无法解释的失败就自动隔离。
- 重复覆盖既看不见又很贵。问三次就得到三条测同一条路径、名字不同的测试。评审里没有任何环节会抓到它,覆盖率报告也不会提;它最终表现为一套要跑十一分钟才说出上季度四分钟就说完的话的测试。
- 合入前先给运行时长标价。一百条各 200 ms 的生成测试,会给每位工程师的每一次 CI 永久加上二十秒。这是个实打实的数字,值得和变异得分一起写进同一条 PR 评论里。
- 果断删,并且让删除变得廉价。大多数生成测试的正确归宿是被拒绝,而一种把"删测试"视作损失的评审文化,会把整本账都攒下来。从未失败过、也不可能失败的测试并不是中性的。
循环里该给它什么,以及绝不能给它什么。
测试生成是一类验证信号异常干净的智能体任务,所以只要边界划对,循环设计本身很直接。
- 它必须运行自己写的测试。没有执行环节的生成会产出编译不过、import 错模块、调用参数个数不对的测试,比例高到让产出物无法使用。给它一个沙箱运行器,把"提交前必须全绿"当作及格线,做法见沙箱与安全执行。
- 喂给它测试约定,而不只是源码。fixture、工厂函数、项目的 mock 策略、那个能构造出合法 user 的辅助函数。没有这些,智能体会重新发明一套早已存在的脚手架,而评审者的时间会花在驳回一套平行的 fixture 生态上,而不是花在判断断言好不好上。上下文里放两个写得好的现有测试文件,胜过任何指令。
- 想要规格的时候,就把实现藏起来。这是整页里最有用、也几乎没人去拉的那根杠杆。把签名、docstring 和 issue 给智能体,把函数体藏掉:它没法再抄行为,只能写出这个函数应该做什么——而凡是它的猜测和你的代码不一致的地方,两者之中必有一个是值得一看的 bug。这些分歧本身就是产出。
- 绝不允许一次运行里同时改测试和改代码。被要求"把测试跑绿"的智能体就会把测试跑绿,而最省事的改法通常是改断言。把两种运行分开、把两份 diff 分开,并在生成任务里禁止修改已有测试——这与补丁生成与测试驱动循环里的隔离论证是同一条。
- 把批量大小压到"评审得动"的尺寸。一个 PR 里四十条测试只会被扫读,而被扫读过的生成测试比没有测试更糟,因为它们背着"已经过评审"的权威。十条、按变异体击杀数排序,才是一个真的有人会读的 PR。
四个数字,没有一个是覆盖率。
测试生成工具默认汇报的指标是"写了多少条测试"。这个数字可以一边上涨、一边让测试变得更差,所以请把下面这些放上仪表盘。
- 改动行上的变异得分。唯一能说明"测试是否钉住了行为"的度量。按 PR 追踪而不是按仓库追踪,它才是闸门而不是趋势线。
- 生成测试的合入率。合入数除以提交数。低不代表失败——它通常说明任务类别选错了,而不是模型不行。接近 100% 则说明没人在读。
- 漏网缺陷率,前后对比。唯一重要的结果指标:每次发布的线上缺陷数。如果一千条新测试没有让它动,那它们就是覆盖率表演,而诚实的回应是把它们删掉,而不是再生成一批。
- 测试总时长与隔离区数量。持有成本。两者都应该持平或下降;只要有一个随测试条数往上爬,账本就在往错误方向走,团队会在一个季度内学会绕开这套测试。
从你即将重构的那个模块的特征化测试开始——那是判据难题彻底消失的唯一场景——并且在别处生成任何一条测试之前,先把针对 diff 的变异闸门放进 CI。然后再扩展到缺陷复现,再到性质测试。不要从"给没测试的模块把覆盖率提上去"开始,那是所有人第一个提出的要求,也是最稳定地产出"一大套全绿测试、认证代码当前所有行为"的那一个。
相关:补丁生成与测试驱动循环看同一个循环的另一个方向,评估编码智能体看这套测试如何变成评测集,代码评审智能体看同一套精确率论证在评审上的应用,以及人工复核的成本看一个四十条测试的 PR 究竟花掉多少钱。