TDD — 红 → 绿闭环 Skill(composable 层)
你的任务
在已约定的 seam 上,把一段行为改动做成一串红 → 绿闭环:一次一个行为,先写失败测试, 再写刚好让它过的最小实现,跑验证,当场把命令与输出写进执行记录。
你交付的不是"代码写完了",而是可核对的证据:每个行为都有实现前的失败输出和实现后的通过输出, 收尾完整跑过一次验证命令。调用方(execute-task 或用户)拿这份证据判定"真的做完了"。
一句话:一个 seam、一个测试、一个最小实现,红了再绿,绿了留证。
调用契约
传入
- 需求依据:要实现什么行为、验收标准、精确值(数字 / 签名 / 用例)。有任务简报就传路径,没有就直接说清。
- seam:测试写在哪个可观察边界上。缺就先与调用方 / 用户确认,不自己挑地方测。
- 验证方式:这段改动跑什么命令验证(如
pytest tests/test_x.py);项目有 typecheck 也一并给。 - 执行记录路径(可选):缺省由
scripts/tdd-record.sh <名称>落在自忽略的.tdd/; 调用方要落在自己的工作目录时传第二个参数(execute-task 传task-N与.execute-task/)。
取回
- 证据齐否:每个行为是否都有红 + 绿,收尾验证命令是否完整跑绿。
- 改动文件清单。
- 疑虑:看到但按规则没动的结构问题。
- 记录路径。
- 卡住时回 BLOCKED + 卡在哪,不硬猜往下写。
本 skill 不做
- 不 commit、不改 tasks checkbox、不动 git 状态——提交是调用方的闸门动作。
- 不排任务顺序、不决定做哪个任务、不改需求或技术决策。
- 不做整体代码审查(那是 review-changes)。
- 不在红绿之间夹带重构。
招牌机制:一次一片 + 当场留证 ⭐
TDD 的价值不在"有测试",而在测试先于实现存在过——红是判据,绿才有意义。两条纪律撑住它:
一次一片(垂直切片)
一个 seam、一个测试、一个最小实现,然后下一个。每个测试都是 tracer bullet: 它回应上一轮学到的东西,而不是照着想象一次列完。 先写完所有测试再写所有实现是水平切片——验的是想象中的行为,对真实改动不敏感。
当场留证
每转一次红或绿,立刻把命令、输出、为何预期失败追加进执行记录。 跑完一整轮再凭印象补写的是回忆,不是证据;上下文一旦被压缩,只有落盘的那份还在。
检验:现在停下来,记录里能不能看出"这个行为在实现前真的红过"?看不出,就等于没走成 TDD。
工作流程
步骤 0 · 锁定 seam 与验证方式
seam 与验证方式开工前必须都有。调用方没给 seam → 现在就写下来与用户确认, 确认后再动手;不在未约定的接缝上写测试。
步骤 1 · 开记录
运行本 skill 目录下的 scripts/tdd-record.sh <名称> [输出目录]:它创建工作目录、
按标准结构建记录文件(已存在则不覆盖,便于续做)、打印路径。把 seam 与验证方式填进记录抬头。
步骤 2 · 逐行为红 → 绿
对每个行为:
- 写失败测试 → 跑 → 看到红。
- 确认红的原因是行为缺失——语法错、import 错、环境没配好导致的红不算红过,先修掉再重跑。
- 当场记红:命令 + 失败输出关键几行 + 为何预期失败。
- 写刚好让它过的最小实现 → 跑同一条命令 → 绿。不提前实现下一个测试要的东西、不加投机功能。
- 当场记绿:同一条命令 + 通过输出。
- 下一个行为。
重构不进循环:让测试转绿的那一步不夹带结构改动。任务边界内的小整理,改完复跑覆盖测试; 结构性问题写进记录的「疑虑」交回调用方(execute-task 会把它带进整体验收的 architecture 轴)。
步骤 3 · 收尾验证
完整跑一次验证命令(加 typecheck / 相关测试文件),输出里的告警也要看见—— 不拿红绿循环中途某一次的输出顶替,那时的代码还不是最终代码。完整输出写进记录的「收尾验证」。
步骤 4 · 交回
按下面「交付判据」自核,再把证据齐否 / 改动文件 / 疑虑 / 记录路径交回调用方。
seam:测在哪,事先约定
- seam = 能从外部观察行为的公共边界(纯函数、模块接口、注入点)。测试住在 seam 上,不伸进内部实现。
- 不在未约定的 seam 上写测试:seam 由上游设计 / 任务指定;没指明就先与用户确认再开工。 测错地方比测得少更糟——本闭环里没有 reviewer 会替你发现测的是外壳还是内部实现。
- 难测的外壳(IO / 框架 / 网络)不硬测:把行为挤到 seam 内侧,外壳留薄。
好测试与三个反模式
好测试:验行为不验实现 / 读起来像规格 / 期望值来自独立事实来源 / 能活过重构 / 一个测试一个逻辑断言。
三个反模式,认出来就换掉(识别信号与改法见 references/test-quality.md):
- 实现耦合:mock 内部协作者、测私有方法、断言调用次数 / 顺序、绕过被测接口从侧信道验证。
- 同义反复:期望值按代码的同一套算法重算一遍——它按构造必然通过,永远不可能与代码不一致。
- 水平切片:先写完所有测试再写所有实现。
mock 只在系统边界:外部 API、时间 / 随机,(有时)数据库与文件系统。不 mock 自己的模块和内部协作者。
小步推进与验证节奏
- 不一次写完整功能;不堆到约 100 行才第一次跑测试。每一步保持系统可构建、可测。
- 未完成但需合并的功能,用 feature flag 挡住,别让半成品阻塞主干。
- 验证按从快到慢分层,快的频繁跑:typecheck(改完随手跑)→ 单测文件(当前这一片)→ 全套测试(闭环末尾 / checkpoint)。
bug 类改动:复现测试优先
- 建立紧凑反馈循环(能快速复现)。
- 复现并最小化问题。
- 提 3~5 个可证伪假设。
- 定向插桩验证假设。
- 写复现测试,确认它红。
- 修到绿,并清理调试代码。
第一个测试就是复现测试——保证修对了、且不回归。
交付判据(交回前自核)
- 红 → 绿证据齐:每个行为都有实现前的失败输出(含"为何预期失败")+ 实现后的通过输出,且都是当场写的。
- 收尾验证实跑绿:完整跑过一次验证命令,看了退出状态与告警。
- 没在红绿之间夹带重构,看到的结构问题进了「疑虑」。
只有绿没有红 → 这个行为没走成 TDD:重做一遍红 → 绿(必要时 git stash 掉该行为的实现,
让测试真的红一次);做不到就在记录里写明并告知调用方,别事后补一段测试冒充红证据。
卡住的升级阶梯
反复实现不过 / 需求歧义 / 外部依赖不可用 → 回读需求依据 → 拆更小的行为 → 交回 BLOCKED 说明卡在哪。 不许什么都不变原样重试,也不许硬猜往下冲。
反例(不要这样做)
❌ 不写测试直接糊实现,或先实现完再补一批测试然后声称做了 TDD —— 红没发生过,证据造不出来。
❌ 先把所有测试写完再写所有实现(水平切片)—— 验的是想象中的行为。
❌ 期望值用被测代码的同一套算法重算一遍(toBe(items.reduce(...))、按同样方式手推的快照)。
❌ mock 自己的模块 / 内部协作者、断言"某方法被调用过"、绕过被测接口直接查库验证。
❌ 在没约定过的 seam 上测(自己挑地方、去硬测 IO 外壳或私有实现)。
❌ 把重构和让测试转绿混在一步做 —— 结构问题记进疑虑。
❌ 红 → 绿边做边过,事后才凭印象把证据补写进记录 —— 追写出来的不是证据,是回忆。
❌ 记录里只写"测试都过了",没有命令与输出。
❌ 一次性写完整功能、堆到几百行才跑一次测试。
❌ 自己 commit / 改 tasks checkbox / 动 git 状态 —— 那是调用方的闸门动作。
❌ 卡住了反复原样重试、空转,不交回 BLOCKED。
自检清单
- seam 是约定过的(来自设计 / 任务,或已与用户确认)?
- 一次一片(一个测试 → 一个最小实现 → 下一个),不是先写完所有测试?
- 每一轮的红都确认过失败原因是行为缺失,不是语法 / import / 环境错?
- 每转一次红 / 绿都当场写进了执行记录,不是事后追写、也不是只留在对话上下文里?
- 期望值来自独立字面量 / 规格,不是按代码算法重算?mock 只在系统边界?
- 让测试转绿的那一步没夹带重构,结构问题进了「疑虑」?
- bug 类改动先写了复现测试?小步推进、每步可构建可测?
- 收尾完整跑了一次验证命令(含 typecheck),输出进了记录?
- 没有 commit、没改 checkbox、没动 git 状态?
- 交回时说清了证据齐否、改动文件、疑虑、记录路径(或 BLOCKED 原因)?
相关参考
- references/test-quality.md —— 好测试判据、三个反模式的识别信号与改法、 mock 边界、期望值来源。
- references/record-format.md —— 执行记录的结构与写法(照抄填空)。
scripts/tdd-record.sh—— 建工作目录 + 按标准结构建记录文件(已存在不覆盖),打印路径。
分层与调用方
- composable 层能力单元:通常由 workflow 层的 execute-task(阶段 2 每个任务)调用, 也可被用户直接触发("用 TDD 把这个改动做掉")。
- 调用是加载执行纪律,不是派 subagent:调用方在自己的上下文里走完本闭环。
- seam 与验收标准来自上游 make-design / split-task;整体审查交 review-changes; 提交、进度回写与收尾都归调用方。