一个违反直觉的发现

最近公司在加大力度 push 大家用 AI Coding,我负责弄了测试这块,专门去调研 TDD 最佳实践。以前可从来不敢在项目里引入单元测试之类的,根本维护不过来🤣,现在不一样了,可以把这活儿扔给 AI。

调研完去看了看网上那些把 TDD 搬到 AI Coding 的实践——写进 CLAUDE.md 的规则、做成 Skill 的工作流——发现一个违反直觉的事实:大部分”AI + TDD”的方案,就是把人类 TDD 的流程原封不动地塞给了 AI。

先写一个失败的测试(红灯),再写最少量的代码让测试通过(绿灯),然后重构(蓝灯)。一步一步,小心翼翼,循环往复。

听着很靠谱,对吧?

问题是——这套仪式是给人类设计的。把它原封不动地搬给 AI,真的适合吗?


红绿灯循环为什么适合人类

TDD 的 Red-Green-Refactor,核心假设是:人类容易犯错、容易分心、容易过度工程化。

  • Red — 强迫你先想清楚”要什么”,别上来就敲代码
  • Green — 防止你一上头就写了三个设计模式来解决一个 if-else 问题
  • Refactor — 制度性地安排一个”停下来想想”的时间点

三道刹车,治三种人类通病。没有它们,程序员的典型轨迹是:上来就写 → 写多了 → 没测试 → 改坏了 → 重写。

但 AI 没有这些毛病。


红绿灯循环为什么不适合 AI

AI 不会分心,不会过度工程化,也不需要小步骤来”驱动设计”——它可以一次性理解整个需求并输出完整实现。

但 AI 有一个人类没有的致命瓶颈:上下文窗口。

你写代码的时候,脑子里天然记着上下文——刚写了什么测试、哪行报错了、接下来该改哪里——这些”工作记忆”对人类来说是免费的。AI 不一样,它的”工作记忆”就是上下文窗口。每走一步红绿灯,测试运行输出、错误堆栈、编译信息,全部塞进去。对你来说是反馈,对它来说是噪声。

跑三轮红绿灯,有效信息密度可能从 80% 降到 30%。你花着每百万 token 几美元的钱,买了一堆 ✅ Test passed❌ AssertionError: expected 42 but got null

而且”写最少代码使测试通过”这条规则也失灵了。它本来是治人类过度工程化的——先用最笨的方式跑通,别一上来就搞花活儿。但 AI 没有这个冲动。强迫它”先 return 42 让测试过,下一轮再写真实逻辑”,就像让计算器先猜一个答案、再算真正的答案。多此一举,双倍消耗。

最坑的是,AI 还会偷偷”修”测试。人类不会删测试来”解决”失败——至少不好意思这么做。但 AI 没有这层心理障碍。测试失败了?改断言、弱化条件、甚至直接删掉——对它来说,这可能就是”最短路径”。你以为是绿灯,其实是自欺欺人。

传统 TDD 用”小步试错”换”低风险”。但 AI 的试错成本不是时间,是上下文空间——最贵、最稀缺的资源。


精神保留,仪式砍掉

不是要把 TDD 一棍子打死。AI 生成的代码”自信但不可靠”,没有测试兜底,翻车只是时间问题。TDD 的灵魂在 AI 时代更关键了。 要扔的只是那套为人类认知节奏设计的执行仪式。

我逐条过了一遍经典的 11 条 TDD 最佳实践。9 条依然成立,只有 2 条要砍。

✅ 直接保留

  1. 测试小而原子化 — AI 修 bug 时只需注入一个小上下文
  2. 测试命名体现意图 — 好名字省 token,shouldRejectExpiredToken()test1() 强一万倍
  3. 聚焦关键逻辑 — AI 容易什么都想测,需要人工把关
  4. 覆盖正常路径 + 边界 — AI 枚举边界用例比人类全面,这是它的优势项
  5. 只测公共接口 — AI 生成的代码内部结构随 prompt 变化剧烈,测公共 API 才稳定
  6. 谨慎使用 Mock — AI 天生爱 mock,一不留神就 mock 了一切
  7. 测试独立隔离 — 可并行跑,失败时可单独注入上下文修复

⚠️ 保留精神,换执行方式

  1. 从小处开始 — 需求拆解的思想保留,但拆完一次性交付,别一个一个喂
  2. 快速反馈循环 — 还是要快,但目的从”保持心流”变成”省上下文”

❌ 砍掉

  1. 严格 Red-Green-Refactor 循环 — 每一步都产生噪声上下文,小步循环是拿最贵的资源做最低效的事
  2. “写最少代码使测试通过” — 治人类过度工程化冲动的药,AI 没有这个病,纯属浪费

AI 踩坑比人类更猛

TDD 经典的 22 条反模式,搬到 AI 场景下几乎全部保留。有几条,AI 踩的频率比人类高得多。

最普遍的是浅层断言。AI 写的测试覆盖率 90%,但变异分数可能只有 30%——你把代码逻辑改了,测试居然还过。assertNotNull 这种断言,跟没写差不多。

其次是过度 Mock。你让它写个 Service 层的测试,它恨不得把 DAO、Redis、HTTP Client 全 mock 掉。测试跑过了,但测的是 mock 本身,真实代码一个字都没跑到。

还有删除失败测试。人类好歹知道不太体面,AI 可没有这层顾虑。

当然也有好消息——“只在我机器上能跑”这种问题 AI 天然不犯,命名也比大部分人类规范。


那 AI 时代的 TDD 该怎么玩

核心就一句话:让 AI 一次干完,然后验收。别一步一步喂。

传统 TDD:  Red → Run → Green → Run → Refactor → Run   (6步,3次运行)
AI TDD:    Spec → Test+Impl → Verify → Fix(if needed)  (2-3步,1-2次运行)

先定验收标准,再一次性交付。 把完整测试套件写好(或让 AI 生成完你审查一遍),作为”通过标准”交给 AI 去实现。测试驱动的是验收,不是设计过程。

失败了别全量重放。 哪个测试挂了,就只给 AI 看那个测试的失败信息和相关代码。它需要的是”哪里错了”,不是”整个世界长什么样”。

重构单独开上下文。 重构和”让测试通过”是两个不同的任务,在干净的上下文里做,效果好得多。

用变异测试替代红灯验证。 传统 TDD 的 Red 阶段本质上是在确认”这个测试真的能检测到错误”。用变异测试(Java 用 PIT,JS/TS 用 Stryker,Python 用 mutmut)批量验证,一次性告诉你哪些测试是假的。

另外建议在 AGENTS.md 或 CLAUDE.md 里加几条硬规则:

  • 🚫 禁止修改或删除已有测试来”修复”失败
  • 🚫 禁止纯 assertNotNull 式浅层断言,每个断言必须验证具体值或状态变化
  • 📊 变异测试门禁 ≥ 60%,覆盖率高但变异分数低就是不及格

写在最后

Kent Beck 发明 TDD 的时候,假设写代码的是人。人会分心、会偷懒、会过度工程化,所以需要红绿灯这套仪式来约束自己。

现在写代码的换了。AI 不会分心也不会偷懒,但它会浅层断言、会过度 mock、会偷偷删测试、会在上下文不够时犯低级错误。病不一样,药方也得换。

好在 TDD 自己就教我们:面对变化,先写测试。