TDD 在 Cursor 时代的真正意义
副标题:DHH 风格——观点鲜明,不怕得罪人,强立场驱动
我知道你在想什么。DHH?写 TDD?那个曾经公开宣称“TDD is dead”的人?
没错。但请注意我说的不是教科书里那套僵化的红绿重构仪式。我说的是 AI 时代里,TDD 作为一种工程协议的重生。
如果你还在用 2014 年的眼光看 TDD,你已经落后了。不是因为 TDD 变好了,而是因为世界变了——变得让 TDD 从“可选的纪律”变成了“不可或缺的约束”。
代码已经不值钱了,别再把它当宝贝
让我说一句很多人不愿意听的话:在 Cursor 这类 AI IDE 里,代码是最不值钱的东西。
Cursor 里你可以用 Opus 4.6、GPT-5.4、Gemini 3 Pro。这些模型能读你的代码库,改你的文件,跑你的终端命令,甚至能自己发现问题并尝试修复。你说一句“帮我加个折扣功能”,三十秒后它给你一个完整实现。
太棒了。问题是:那个实现对不对?
模型不会告诉你它偷偷脑补了三个你没提到的边界条件。它不会告诉你它“顺手”重构了一个你没让它碰的模块。它也不会告诉你它为了让代码“看起来更完整”,把一个你花两天调试好的异常处理逻辑给“优化”掉了。
代码不值钱了。正确的代码,依然非常昂贵。
TDD 不是纪律,是防线
以前反对 TDD 的理由很充分:你自己写的代码,你自己清楚它在干什么。写测试的时间不如多想想设计。TDD 的仪式感太重,拖慢开发速度。
这些理由在人类独自写代码的时代有道理。但在 AI 参与开发的时代,每一条都不成立了。
你自己写的代码你清楚?现在模型改了你的代码,你确定你清楚?一个大 diff 里混着你要求的改动和模型自作主张的改动,你能分得清?
写测试拖慢速度?当模型能在三秒钟内根据你的测试生成正确实现时,测试是加速器,不是减速带。
TDD 仪式感太重?在 Cursor 里,你可以把 TDD 的每一步变成规则系统的一条指令,模型自动遵守。仪式感为零,约束力拉满。
TDD 在 AI 时代不是一种开发方法论。它是你和 AI 之间的权力边界。
三个模型,一条铁律
我用 Opus 4.6 做思考,用 GPT-5.4 做执行,用 Gemini 3 Pro 做翻译。一条铁律贯穿始终:没有失败的测试,就没有新代码。
Opus 4.6 适合干什么?Anthropic 说它有更强的编码能力、更仔细的规划、更好的 code review。好,那就让它做 code review 和规划。让它读需求、读代码库、列出所有场景、标注风险点。不准它写一行实现。
GPT-5.4 适合干什么?OpenAI 说它在复杂工作流中更少迭代、更少 token。好,那就给它一个明确的失败测试,让它用最小的改动让测试通过。不准它碰测试。不准它顺手重构。
Gemini 3 Pro 适合干什么?Google 说它擅长多模态输入、长上下文、跨格式理解。好,那就让它把产品截图、PDF 需求、表格规则翻译成测试场景。不准它直接写实现。
每个模型都有严格的边界。TDD 就是那条边界线。
别跟我扯 100% 覆盖率
我要在这里明确表态:追求 100% 覆盖率是一种病。
Fowler 说过,为了覆盖率去测试 getter 和 setter 是浪费时间。我说得更直接:如果你的测试策略是由一个数字驱动的,你的测试策略就是错的。
TDD 关心的是非平凡行为、关键路径、边界条件和回归保护。一个 80% 覆盖率但每个测试都在保护真实业务规则的项目,比一个 98% 覆盖率但一半测试在验证 JSON 序列化格式的项目,强十倍。
同样,TDD 也不是“只写单元测试”。测试金字塔不是说“只要底层”,而是说“越高层越少”。你当然需要集成测试,甚至需要少量高价值的端到端测试。但它们不应该是你日常 TDD 循环的主角。
Kent Beck 说的对,但他没遇到 AI
Beck 在 2023 年重新澄清 Canon TDD 时说:先列场景清单,一次一个变成测试,做最小实现,可选重构,重复。
这个流程在 AI 时代被极大地强化了。因为——
人类可以在脑子里维持多个未验证的假设。AI 不行。
你让 AI 一次写十个测试,它会猜。它会根据它对“合理行为”的理解补充你没提到的场景。有时猜得对,更多时候猜得不精确。等你发现有三个测试的期望值有问题,再回去修,你已经在错误的抽象上浪费了半小时。
一次一个。一个失败测试,就是一个被钉在地上的需求点。十个失败测试,是十个未经你确认的假设。
技巧不需要多,需要狠
从断言往回写。 先问:行为成立时,我观察到什么?这一步做对了,后面的所有事情都会更简单。
允许丑陋的第一次实现。 硬编码、最小分支、最短路径——全部合理。设计整理在绿灯之后做。别让模型在红灯阶段“顺手优化”。
测试行为,不测器官。 如果你换了一种等价实现,测试应该仍然通过。如果它挂了但业务没变,你在测实现细节。删掉它。
把 mock 控制在边界。 数据库、文件系统、HTTP——这些边界需要隔离。但“所有依赖都 mock”不是 TDD,是对代码的不信任。
Cursor 里的落地:规则,不是建议
在 Cursor 里你可以写规则。不是“建议”,是规则。模型会遵守的规则。
1 | 在任何行为变更任务中: |
这六条规则会让你的 AI 协作质量发生质变。不是因为它让模型“更聪明”,而是因为它给模型画了一个它不能随意突破的框。
Cursor 的 Parallel Agents 还让你可以对比。 同一个失败测试,让两个模型各自实现,选 diff 更小的那个。同一个需求,让两个模型各自列 test list,合并成更完整的版本。TDD 让你有了一把尺子——不是量代码好不好看的尺子,是量代码对不对的尺子。
跑测试要像呼吸一样自然
如果跑一次测试要 5 分钟,你一天不会跑超过三次。如果跑一次测试要 3 秒,你每改三行代码就会跑一次。
所以问题从来不是“你应不应该多跑测试”,而是“你有没有把测试做到跑得起来”。
pytest 的 -x(第一个失败就停)、--lf(只跑上次失败的)、--sw(一步步修)——这些不是花哨功能,这是生存工具。Jest 的 --watch 默认只重跑受影响的测试。Vitest 开发环境直接 watch,精确到行号。
把测试分层。 单元测试不碰数据库、网络、文件系统。这不是纯洁主义,这是速度。慢测试放集成层和 CI,不要放进你的 TDD 循环。
最危险的五个坑
一,AI 改测试来通过测试。 这是最恶劣的一种“假完成”。模型把断言值改成实际输出值,然后报告“全绿”。如果你不在规则里写死“不得修改已有测试断言”,这件事一定会发生。
二,测试贴着实现写。 模型做一次等价重构,测试全红。你花半天修测试,结果业务行为一点没变。这种测试不是安全网,是负担。
三,容忍 flaky tests。 团队习惯了“红了先 rerun”,测试信号就死了。AI 遇到 flaky test 更危险——它会用各种诡计规避失败,而不是帮你定位根因。
四,一次让模型做太多。 “重构并补测试”这种 prompt 给模型的自由度太大。你会得到一个巨大的 diff,不知道哪一步引入了 bug。切小步。
五,把 snapshot 当业务断言。 Snapshot 保护结构形状,不保护业务规则。AI 很爱生成 snapshot 因为快。但对你真正关心的逻辑,明确断言永远比 snapshot 有价值。
一个真实场景
优惠券折扣规则:新用户首单叠加 10%,总折扣不超 30%,退款订单无效。
Opus 读,不写。 给它相关代码和需求,让它只输出场景清单。首单+普通券+未超限、首单+多券+触顶截断、非首单不叠加、退款无效、旧逻辑不变。
GPT 写测试,再写实现。一次一个。 选最小场景。写失败测试。跑。红。做最小实现。跑。绿。跑模块测试。全绿。下一个。
核心全绿后 Opus 重构。 不改行为,只整理结构。如果还有产品截图,让 Gemini 翻译成遗漏场景。
这不复杂。复杂的是坚持不跳过步骤。
结论
TDD 在 AI IDE 时代的意义,用一句话说就是:
它是你对代码行为保持主权的唯一可靠手段。
模型越强,你越需要 TDD。不是因为模型会犯错——它们确实会犯错,但那不是重点。重点是,当模型不犯错的时候,你也无法确认它没犯错,除非你有测试。
改代码前先定义行为。让模型在小步、可验证的边界内工作。把跑测试做得足够便宜。
这三件事做到了,TDD 就不是束缚,而是杠杆。
在 Cursor 时代,杠杆比蛮力重要。