Pi实战 12:TDD 测试驱动篇------用 Pi 做测试驱动开发
来源:Daniel Weinmann、Marcos Lobo、tweag《Agentic Coding Handbook》、VS Code TDD 指南,以及 Martin Fowler 对"Agent 循环内 TDD"的质疑(保持客观)。
一、使用场景
- 让 Agent 加新功能,结果它悄悄改了旧行为还没人发现。
- 改完不敢动,怕回退,因为没测试兜底。
- 想要"Agent 写的代码至少行为是对的",而不是靠人眼 review 每一行。
- 想有一个清晰的"完成"标准,而不是一句"改好了"。
二、能解决什么问题
TDD(测试驱动开发)给 Agent 一个不会骗人的验收标准:先写一个会失败的测试(定义"什么算对"),再让 Agent 写代码把它跑绿。
它顺带解决两件别的事:
- 逼你澄清需求:写测试的过程就是想把"这段代码到底该干嘛"讲清楚的过程。
- 给后续重构兜底:有测试在,你(和 Agent)以后改代码才有安全感。
三、解决思路
思路 1:红 - 绿 - 重构的最小循环
经典 TDD 三步,对 Agent 一样适用:
1. Red ------ 写一个会失败的测试(此时功能还没实现)
2. Green ------ 让 Agent 写"刚好能让测试通过"的最少代码
3. Refactor------ 保持测试绿的前提下,清理代码
tweag 的《Agentic Coding Handbook》点出它和 Agent 特别搭:
"It keeps you in flow: Tests give you checkpoints. Instead of stopping to debug a vague output, you just write the next test and let the AI catch up.
It reinforces clean, behavioral thinking: TDD forces you to describe what the code should do, not how to write it. That's exactly how we should prompt LLMs."
(测试给你检查点,让你保持心流:不用停下来调试模糊的输出,写下一个测试让 AI 追上就行。它还强化"行为化"思维:TDD 逼你描述代码该做什么 ,而不是怎么写------而这正是我们该给 LLM 的提示方式。)
思路 2:你写验收测试,Agent 负责实现
Marcos Lobo 的极简工作流(最适合起步):
1. 你写验收测试(acceptance tests)
2. 让 Agent 实现让这些测试通过的代码
3. 你 review 生成的代码,然后迭代
思路 3:把"完成"定义死
Daniel Weinmann 的 Definition of Done,直接写进 AGENTS.md:
- TDD is non-negotiable. Always write the tests first and ensure they fail before implementing.
Move in baby steps through the red-green-refactor cycles.
- A task is not done if the linter is not passing for the whole project.
- A task is not done if it has new behavior without tests to ensure that new behavior.
翻译:
- TDD 不可商量:永远先写测试、确认它失败,再实现;小步快跑红-绿-重构。
- 整个项目 linter 没过 = 没完成。
- 有新行为却没对应测试 = 没完成。
四、如何实战
实战 0(手把手):从零走一遍"红 - 绿 - 重构"
前面讲的都是概念。下面用一段能直接复制、能真跑的例子,把整个循环从头走一遍。我们用 Python + pytest(最直观,换成 JS/Go/Java 逻辑一模一样)。
场景 :写一个"购物车结算"函数 calculate_total(items),规则是"各商品 price×qty 求和,满 100 减 10"。
准备(一次就好):
bash
mkdir tdd-demo && cd tdd-demo
pip install pytest # 装测试框架;JS 项目就用 npm install -D jest/vitest
touch cart.py # 先建一个空文件
下面每一步,我们都先写测试 → 跑测试看它红 → 写最少代码 → 跑测试看它绿 。命令统一用 pytest -q(-q 表示安静模式,红的绿的很清楚)。
第 1 步:写第一个测试(空购物车 = 0)
test_cart.py:
python
from cart import calculate_total
def test_empty_cart_is_zero():
assert calculate_total([]) == 0
跑:
bash
$ pytest -q
输出是红 的,因为 calculate_total 还不存在:
E ImportError: cannot import name 'calculate_total' from 'cart'
白话 :测试一跑就红,这正是我们想要的。它证明"这个测试真的在测东西"------如果这时候它就绿了,反而说明测试没测到点上。先确认它会失败,是 TDD 的第一铁律。
第 2 步:写"最蠢"的代码让它绿
cart.py:
python
def calculate_total(items):
return 0
跑 pytest -q → 绿。
白话 :别笑
return 0太蠢。TDD 的精髓就是先迈最小的一步:现在框架通了、函数名对了、空购物车这个用例过了。这一步的价值是"确认地基没问题",不是"写出完整逻辑"。
第 3 步:加第二个测试(一件商品)
在 test_cart.py 追加:
python
def test_single_item():
assert calculate_total([(10, 2)]) == 20 # 单价 10,买 2 件
跑 pytest -q → 又红 了(return 0 不等于 20)。
白话:新测试红是正常的、是好事------说明它在真正干活,逼着我们往下写。
第 4 步:让实现"刚好"满足所有测试
cart.py:
python
def calculate_total(items):
total = 0
for price, qty in items:
total += price * qty
return total
跑 pytest -q → 全绿。
白话 :这段逻辑是被测试一步步逼出来的,不是凭空拍的。这就是 TDD 的核心体验------"测试告诉你要写什么,你只写让测试过的那一点"。
第 5 步:加"满减"测试(满 100 减 10)
test_cart.py 追加:
python
def test_discount_over_100():
assert calculate_total([(60, 1), (60, 1)]) == 110 # 共 120,满 100 减 10
跑 pytest -q → 红(现在算出 120,不是 110)。
白话:这个失败精准地告诉你"满减规则还没实现",而且只告诉你这一件事。
第 6 步:补上满减逻辑
cart.py:
python
def calculate_total(items):
total = 0
for price, qty in items:
total += price * qty
if total >= 100:
total -= 10
return total
跑 pytest -q → 全绿。功能齐了。
第 7 步:重构(不改变行为,只让代码更干净)
回头看代码:100、10 这种"魔术数字"埋在代码里,别人看不懂规则。把它们提成常量:
cart.py:
python
DISCOUNT_THRESHOLD = 100 # 满减门槛
DISCOUNT_AMOUNT = 10 # 减免额
def calculate_total(items):
total = sum(price * qty for price, qty in items)
if total >= DISCOUNT_THRESHOLD:
total -= DISCOUNT_AMOUNT
return total
跑 pytest -q → 还是全绿。
白话 :重构不改任何行为 ,只是让代码更好读。为什么敢放心改?因为有测试兜底------万一改坏了,
pytest立刻变红提醒你。这就是测试给重构提供的"安全感"。
这一遍走下来你得到了什么?
每一步都被测试推着走,最后的代码是被一整串测试证明过 的。以后谁想改 calculate_total,只要不跑绿测试,就不算改完------这就是给 Agent(和人)的"不会骗人的验收标准"。
实战 1:把上面交给 Pi,用 checkbox 计划驱动它
看懂循环后,就可以让 Pi 自己跑了。把刚才那套写成带勾选框的 TDD 计划丢给它,它会一项项"先测试后实现":
markdown
# TDD Plan: 购物车结算 calculate_total
用 pytest,严格红-绿-重构,每实现一步都要跑 pytest 确认变绿。
- [ ] 写测试:空购物车返回 0;先跑,确认它红
- [ ] 写最少代码让它绿
- [ ] 写测试:单件商品 (price, qty) 返回 price × qty;确认红
- [ ] 实现求和逻辑,跑测试确认绿
- [ ] 写测试:满 100 减 10(例如 [(60,1),(60,1)] == 110);确认红
- [ ] 实现满减,跑测试确认绿
- [ ] 重构:把 100 / 10 提成常量,跑测试确认仍绿
关键:让 Pi "每步都跑测试并贴结果给我看",你只当那个"看红了没、绿了没"的人。
实战 2:分步下指令------把红/绿/重构拆成三步
不想一次给一整份计划,也可以对 Pi 分三次下指令,逼它一次只干一件事(这是最不容易跑偏的用法):
第 1 句:先只写「空购物车返回 0」和「单件商品返回 price×qty」这两个失败测试,
别写实现,写完后跑 pytest 把红色的报错贴给我。
(你确认测试确实红了、且测的是你想要的行为之后)
第 2 句:现在写最少代码让这两个测试变绿,跑 pytest 把绿色结果贴给我。
(变绿之后)
第 3 句:在测试保持全绿的前提下,重构这段代码让它更好读,再跑一次确认还是绿的。
一次只让它做"写测试 / 写实现 / 重构"中的一件,比一句话"用 TDD 帮我加个功能"稳得多------后者它经常假装在做 TDD,其实一把梭。
实战 3:先审测试,再让它写实现(human-in-the-loop)
最稳妥的一道保险:让 Pi 先把测试写给你看,你点头了它再写实现:
你先只写测试,把测试代码贴给我。
我确认测试覆盖了我想要的所有行为之后,你再写让这些测试通过的代码。
实现过程中如果测试红了,先把红色报错贴给我,别自己闷头瞎试。
这样即使 Pi 理解偏了,也只会偏在"测试"这一层(改几行测试就行),不会连带写出一大坨错的实现让你返工。在测试层拦跑偏,比在实现层拦省力得多。
五、一个必须告诉你的争议(保持客观)
TDD 在 Agent 循环里是不是真有价值,社区是有争论的,别把它当圣经。
Martin Fowler 在 2026 年的文章里坦率地说:
"I personally have stopped telling my coding agents to write tests first, let alone do TDD... until I see evals or other strong arguments that convince me otherwise. I'm trying to focus instead on the benefits of TDD when I use it outside the agent loop."
(我个人已经不再让我的编码 Agent"先写测试"了,更别提完整做 TDD------除非我看到 evals 或其他强有力的证据。我现在更关注在Agent 循环之外用 TDD 带来的好处。)
怎么理解这个分歧:
- 在 Agent 循环内强制每一步都红-绿-重构,可能是"仪式感大于价值"(theater),甚至拖慢它。
- 在 Agent 循环外 ,TDD 的价值很实在:你用测试当验收标准、当重构安全网、当回归测试------让 Agent 跑你的测试,看它有没有破坏既有行为。
给你的可操作结论:
- 不必迷信"每一步都严格 TDD"。
- 至少做到:新行为要有测试覆盖(Daniel 的 Definition of Done)、用既有测试 suite 当 Agent 的回归护栏。
- 想让 Agent "写测试优先"时,用 checkbox 计划 + 先审测试再实现,最稳。
六、小结 & 避坑
- 红-绿-重构是最小循环;测试是给 Agent 的"不会骗人的验收标准"。
- 最简起步:你写验收测试 → Agent 实现 → 你 review → 迭代。
- 定义死"完成":linter 没过 / 新行为无测试 = 没完成。
- 先审测试再实现:在测试层拦跑偏,比拦实现更省力。
- 别迷信循环内 TDD:Fowler 已不再强制 Agent 每步 TDD;把测试当循环外的验收/回归护栏更实在。
上一篇:11 · 上手陌生代码库篇
参考资料
- Daniel Weinmann(LinkedIn,TDD 不可商量 + Definition of Done):https://www.linkedin.com/posts/danielweinmann_brad-w-asked-me-how-i-make-my-coding-agents-activity-7356066470478471168-Jfwc
- Marcos Lobo《Workflow with TDD and AI Agents》:https://ainativecompass.substack.com/p/workflow-with-tdd-and-ai-agents
- tweag《Agentic Coding Handbook --- Test Driven Development》:https://tweag.github.io/agentic-coding-handbook/WORKFLOW_TDD
- VS Code《Set up a test-driven development flow》:https://code.visualstudio.com/docs/agents/guides/test-driven-development-guide
- Martin Fowler《TDD inside the agent loop --- theater or actual value?》:https://martinfowler.com/articles/exploring-gen-ai/tdd-in-the-agent-loop.html