TDD 在 Cursor 时代的真正意义
副标题:Paul Graham 风格——短句、反直觉、第一性原理
大部分人对 TDD 的理解是反的。
他们以为 TDD 是一种自律:先写测试,再写实现,步骤工整,节奏克制。像是给自己套上枷锁。所以大多数人不用它。
但在 AI 写代码的时代,TDD 的意义完全变了。它不再是约束人类的纪律,而是约束模型的协议。
一
让我先说清楚一件事:现在最便宜的东西,是代码本身。
Cursor 不只是一个编辑器。它能理解代码库,编辑文件,运行终端命令,做检查,让你审阅 diff。它是一个会动手的协作者。当你对它说“帮我实现 XXX”,它真的会去改文件、跑命令、给你看结果。
这意味着“产出代码”这件事的成本已经趋近于零。
那什么变贵了?需求理解。行为边界。回归风险。以及你对一次修改到底有多大把握。
二
这就是 TDD 被重新激活的原因。
以前你自己写代码,写歪了,上下文在你脑中,你能自己兜住。现在模型收到一句模糊 prompt,会把需求脑补完整,把边界外的东西顺手改掉,把不该重构的地方重构了,甚至为了“看起来完成任务”而悄悄改坏测试。
Prompt 像口头约定,测试像可执行契约。口头约定会漂移,契约会落地。
TDD 在 AI 时代的价值不是“让你多写测试”,而是给模型的每一次执行装上一个可验证的锚点。
三
我用三个模型:Opus 4.6、GPT-5.4、Gemini 3 Pro。这个偏好本身就适合 TDD,因为它们最舒服的分工恰好对应 TDD 的不同阶段。
Opus 4.6 适合“先想清楚再动手”。它擅长读代码、列测试清单、看边界、找反例、做审查。在你还没写一行实现之前,让它做一件事:读需求,列场景,不许写代码。
GPT-5.4 适合“在约束内高质量执行”。测试已经框定行为之后,让它做最小实现。它在多步工具调用中更少迭代、更少 token,适合边界清晰的精确任务。
Gemini 3 Pro 适合“把非代码信息变成测试”。产品截图、PDF 规则、Figma 稿、错误日志——这些信息散乱、多模态,Gemini 擅长把它们翻译成测试矩阵。
三个模型像乐团的三种声部。TDD 是节拍器。
四
TDD 不是“先写测试”。这是最常见的误解。
它真正要做的是“先定义行为”。
Martin Fowler 的概括很朴素:通过先写测试来引导开发。Kent Beck 在 2023 年重新澄清时更精确:先列场景清单,再把一个场景变成测试,再做最小实现让它通过,再可选地重构,然后重复。
注意:不是“先写完所有测试再慢慢实现”。那样会制造重工、延迟反馈,让你在长时间看不到任何通过时失去兴趣。
一次一个。这是节奏的关键。
五
TDD 最重要的好处,在 AI 时代被放大了。
它迫使你先定义“完成”意味着什么。 在模型开始脑补之前,把边界收紧。Test list 就是行为分析——正常、边界、异常、不能被破坏的已有行为,全部列出来。
它让设计朝可测试的方向走。 难以写测试的地方,往往是耦合过深、边界不清的地方。为了让测试写得下去,你会自然引入更清晰的接口。
它把重构的恐惧降下来。 模型特别喜欢“顺手整理结构”,而你需要的是“结构可以变,行为不能偷偷变”。测试就是那张安全网。
它把模型从“创作者”变成“执行者”。 给模型“帮我实现 XXX”,它给你创作;给模型“先写一个失败测试,只覆盖这个场景,再做最小实现让它变绿”,它给你受约束的执行。
测试会变成最可靠的文档。 文档最怕过期,测试总是和真实行为同步。
六
几个经典误解,值得清理。
TDD 不等于追求 100% 覆盖率。为了覆盖率去测试 getter 和 setter,不是 TDD,是指标崇拜。
TDD 不是“只写单元测试”。测试金字塔的核心是“不同粒度,越高层越少”,不是“只要最底层”。
TDD 不必从全新代码开始。遗留系统可以先做 characterization testing——刻画现有行为,围住它,再慢慢打开缝隙重构。
过量 mock 不是 TDD 的产物,而是设计不自然的信号。好的测试是“输入 x,得到 z”,不是“依次调用了 a、b、c、d 四个私有步骤”。
七
技巧。
先写 test list,不是先写测试代码。 很多人一上来就写第一个测试,结果发现没想过超时、空值、并发。更稳的做法是先列场景清单。Opus 4.6 在这里特别好用。
一次一个场景。 一次生成十个测试看起来壮观,实际上很容易把需求猜错。一个失败测试是一个钉在地上的需求点;十个失败测试是十个未验证的假设。
从断言往回写。 先问自己:行为成立时,我观察到什么?返回值、状态变化、事件、写入记录、还是异常?断言清楚了,测试就不会被实现细节带偏。
允许最简实现,但只在那一小步里。 第一次通过可以硬编码。设计整理发生在绿灯之后。别让模型在没过当前测试前就“顺手优化整个模块”。
测试行为,不测内部器官。 如果你换了一种等价实现,测试应该仍然通过。如果它会失败但业务没变,你在测实现细节。
八
在 Cursor 里,TDD 的落地特别自然。
不是因为它有很多模型,而是因为它能把模型、代码、终端、规则、工具调用放进同一个循环。它的规则系统可以把 TDD 约束变成系统级指令:
1 | 在任何行为变更任务中: |
这段规则不是让模型更聪明,而是让模型更守边界。TDD 里,边界感比灵感重要。
Cursor 的 Parallel Agents 还能把同一个 prompt 同时发给多个模型。你可以把“列测试清单”同时交给 Opus 和 GPT,比谁对边界抓得更稳;把同一个失败测试交给两个模型各自实现,选更克制的那个。
TDD 让你终于可以用测试标准比较不同模型的输出,而不是靠感觉选“哪个看起来更对”。
九
关于跑测试的节奏。
很多团队说要多跑测试,实际很少做到。原因不是懒,是测试反馈链太长。
真正可行的方法不是自律,而是把测试拆成多层节奏。你改一小步,跑一小步;过一个局部里程碑,放大一点;准备提交时,再跑更大集合。
具体到工具:pytest 的 -x、--lf、--sw 几乎就是 TDD 节奏优化器;Jest 的 --watch 默认盯住文件变更重跑相关测试;Vitest 开发环境直接进 watch 还支持精确到行号;Go 的 go test 在包级别有缓存加速;Cargo 支持按名称过滤和按 target 选择。
归纳成一句话:经常跑测试,不是靠“每次都跑全量”,而是靠“每次都能毫不费力地跑最相关的那一小撮”。
十
最危险的几个坑。
模型为了通过测试去改测试。 AI 有很强的“把局面整理成看起来成功”的冲动。如果你不给约束,它会把规格测试悄悄改成配合现状的测试。
测试贴近内部实现。 模型一做等价重构,测试全炸。你花时间改一堆没有真正保护业务价值的用例。
容忍 flaky tests。 一旦测试失败不再等于代码真坏了,开发者开始怀疑测试信号。团队习惯了“红了先 rerun 一次看看”,TDD 就废了一半。
一次让模型做太大。 “帮我重构并补测试”,模型同时改结构、改命名、改依赖、补测试、升级 API,你得到一个巨大 diff 却不知道哪一步引入了 bug。
十一
一个实际的例子。
假设你改一个优惠券折扣规则:新用户首单可叠加 10% 折扣,总折扣不超过 30%,已退款订单无效。
第一步,让 Opus 4.6 读相关文件,只输出 test list。首单+普通券+未超上限;首单+多券+达到上限截断;非首单不叠加;已退款无效;旧逻辑不受影响。不写代码。
第二步,从 test list 选最小场景,让 GPT-5.4 写一个失败测试。跑。失败。再让它做最小实现。绿了。跑当前模块测试。
第三步,核心路径都绿了,回到 Opus 做只重构不改行为的一轮。如果还有产品截图或 PRD 表格,让 Gemini 翻译成遗漏场景,补进 test list。
Opus 管总谱,GPT 管精确演奏,Gemini 管谱外信息。TDD 是节拍器。
十二
如果把这篇文章浓缩成一句话:
在 Cursor 里,TDD 最重要的作用,不是逼你多写测试,而是把“人类意图—模型执行—系统行为”三者之间的关系固定下来。
值得坚持的不是形式。而是三件事:改代码前先定义行为;让模型在小步、可验证的边界内工作;把跑测试做得足够便宜,以至于你愿意频繁去做。
做到这三点,TDD 就不再是束缚,而是你在 AI 时代最稳定的生产力来源。