AI助手为刚生成的代码写单元测试会陷入确认偏差——用有bug的代码作ground truth,测试通过但bug被固化。
随着 Claude Code、Cursor 和 GitHub Copilot 等 AI 编程助手成为现代软件工程的日常工具,许多团队的流水线中出现了一个危险的模式:让 LLM 为它们刚刚编写的代码生成单元测试。
表面上看,这很高效。AI 写一个功能、写一套测试、测试运行、流水线变绿。
然而,依赖事后测试生成创造了一个巨大的结构性盲点——软件工程研究人员和核心维护者已经对其进行了量化。
当 LLM 生成一个功能时,它的上下文窗口包含了产生那段代码的确切逻辑、假设和潜在的边界情况遗漏。
如果你紧接着用"现在为这段代码写单元测试"来提示模型,模型就会用自己生成的代码作为基准真相。它不会问"根据业务需求,这段代码应该做什么?",而是问"这段代码现在做了什么?"
如果功能代码中包含一个微妙的逻辑 bug,LLM 会生成验证那个确切 bug 为预期行为的测试断言。流水线通过了,但你所做的不过是把确认偏误自动化了。
这不仅仅是一个理论担忧,软件测试指标已经证明了它:
高覆盖率、低突变检测:使用突变测试(故意向代码注入人为 bug,看测试是否能捕获它们)来评估 LLM 生成的单元测试的研究揭示了一个严峻的现实。虽然事后 LLM 测试达到了高行覆盖率,但它们的突变分数往往很低,有时低于 15%。
绿 Suite 幻象:因为事后测试反映的是实现而非边界规则,即使底层业务逻辑被人为破坏,测试仍然通过。
一个常见的反驳是,将规范需求添加到 CLAUDE.md 等上下文文件中会使上下文窗口膨胀,降低模型性能。
这混淆了两个完全不同的事物:
仓库规则(CLAUDE.md / 系统指南):这应该严格保持轻量,只定义架构模式、技术栈和格式约束。
功能规格(Issue、API 契约、Specs):功能需求应该放在专门的 issue 文件、OpenAPI 契约或功能生成过程中传递的规格文件中。
无论你使用单一 agent 循环还是 sub-agent 设置,可靠 AI 工程的核心规则仍然是测试驱动开发(TDD):
[ 外部规格 / Ticket ] ---> [ 生成断言 / 测试 ] ---> [ 生成功能代码 ] ---> [ 验证 ]
当测试断言在代码生成之前或期间锚定到外部需求时,LLM 被迫根据一个不变的标准来评估其实现,消除了假阳性。
AI 是一个强大的力量倍增器,但如果你的测试套件是为了验证它自己的幻觉而写的,那么绿色的流水线毫无意义。
通过将测试需求与事后代码生成解耦,并强制执行规格优先工程,团队可以在不引入静默生产 bug 的情况下安全地利用 AI。
参考资料与进一步阅读:
Mutation-Guided Unit Test Generation with LLMs (IEEE/ACM International Conference on Automated Software Engineering)
Beyond Functional Correctness: Exploring Hallucinations in LLM-Generated Code
Evaluating Test-Driven Development in LLM-Based Code Generation (ACM Transactions on Software Engineering)