语言协议:以对话语言输出——用户显式指定(含平台
language设置)优先,其次跟随用户近期消息语言;均无法判定时默认英语。落盘产物以创建时对话语言为准,增量修改保持产物既有语言。本 skill 中的固定话术是语义模板,用对话语言表达其意,不逐字照搬。
外部搜索统一入口:需要联网检索(资料、库/框架文档、时效信息)时一律先用 anysearch skill(插件内嵌),不可用再降级 WebSearch/WebFetch;降级链与派发词要求见 requirement-analysis 的 references/exploration-patterns.md。
测试驱动开发(TDD)
概述
先写测试。看它失败。写最小代码让它通过。
核心原则:没看着测试失败过,你就不知道它测的是不是对的东西。
本 skill 只依赖项目自身的测试命令,不依赖任何平台专属工具,Claude Code 与 Codex 通用。
违反规则的字面 = 违反规则的精神。
何时使用
总是:新功能、bug 修复、行为变更走红绿循环;纯重构走本文「收尾纯重构」,不伪造红。
例外清单(单点定义):一次性原型、生成代码、配置文件,以及不改变行为、契约或测试预期的纯文案。例外需要可追溯的用户授权;同一范围内已授权则引用原决定,不反复询问。技能规则、API 语义与测试预期即使写在 Markdown 中,也不是纯措辞。
想着"就这一次跳过 TDD"?停。那是合理化借口。
测试落点确认与消费
测试落点(seam)是观察公共行为的契约边界,可为模块函数、CLI、HTTP API、数据库适配器,不限于最外层界面。优先复用已有落点,选择仍能稳定观察目标行为的较高层接口,尽量少建新接口;一个落点可覆盖多个 Scenario,不逐私有函数制造测试。
spec/plan 已批准的落点直接使用,不重复确认。存量无 seam 标签但批准的精确接口和 Scenario 唯一指向同一边界时,只提取已有决定并记录来源,不迁移旧计划格式或推测新决定。
没有唯一落点或 spec/plan 冲突时,停止受影响任务的测试和实现,主线程走原澄清/偏差流程,不默默择一覆盖。即兴任务在写测试前确认公共接口和落点,quick-fix 并入已有修复确认环节,一次一题。implementer 回报 blocked,不向用户另开门、不改 plan 或扩大 writes。
铁律
没有有效失败测试,就不写新增或改变行为的生产代码
未按纪律先写了行为实现?删除本次未经测试先行的实现,从失败测试重做,不留作参考;不删除无关既有工作。纯重构与已授权例外按对应规则处理,不冒充新增行为的红证据。
红绿循环
digraph tdd_cycle {
rankdir=LR;
red [label="红\n写失败测试", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="确认失败\n原因正确", shape=diamond];
green [label="绿\n最小实现", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="确认通过\n全绿", shape=diamond];
next [label="下一个", shape=ellipse];
red -> verify_red;
verify_red -> green [label="是"];
verify_red -> red [label="失败原因\n不对"];
green -> verify_green;
verify_green -> green [label="否"];
verify_green -> next [label="是"];
next -> red;
}
红——写失败测试
在获批 seam 写一个最小测试:一个公共行为、清晰命名、独立真值。mock 准入遵循 references/testing-anti-patterns.md,不用自己的算法重算期望值。
// ✅ 好:名字表意、测真实行为、只测一件事
test('retries failed operations 3 times', async () => {
let attempts = 0;
const operation = () => {
attempts++;
if (attempts < 3) throw new Error('fail');
return 'success';
};
const result = await retryOperation(operation);
expect(result).toBe('success');
expect(attempts).toBe(3);
});
// ❌ 坏:名字含糊、测的是 mock 不是代码
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
确认红——看它失败
行为红绿路径的强制步骤。 确认失败源于目标行为缺失,而非拼写、编译或环境故障;刻画已有行为遵循「收尾纯重构」,不冒充红证据。
- 行为缺陷测试直接通过了? 核实是否复现目标缺陷并修正无效测试。纯重构刻画应先观察当前行为通过,不适用本条。
- 测试报错了? 修到它以正确原因失败为止。
绿——最小实现
写让测试通过的最简代码。不加多余功能、不顺手重构别的代码、不超出测试要求"改进"。
// ✅ 好:刚好够通过
async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
for (let i = 0; i < 3; i++) {
try { return await fn(); } catch (e) { if (i === 2) throw e; }
}
throw new Error('unreachable');
}
// ❌ 坏:maxRetries/backoff/onRetry 没人要 —— YAGNI
确认绿——看它通过
强制步骤。 运行测试,确认:目标测试通过、其他测试仍通过、输出干净(无报错无警告)。
- 公共行为回归? 修实现。已核实行为未变但测试绑定私有细节时,按反模式 7 修正落点,不迁就脆弱断言。
- 其他测试挂了? 现在就修。
重构候选
发现重复、命名或结构清理机会时,记录位置、理由与保护证据,交既有收尾,不插入每次红绿循环。每票第五步仍为提交;契约自检只查 over/under-building 与接口锚定。
重复
为下一个行为写下一个失败测试。
批准的前置纯重构与集成组
批准计划中解除实际实施阻碍的前置纯重构在 T01 执行,复用下方前后公共行为保护规则;不能等到收尾才解除阻碍。执行中额外发现的重构机会仍交收尾,不借此前置规则扩大授权。
集成组不是 TDD 例外:行为变化在实现前仍获得有效行为失败;纯机械/行为保持迁移先取得组基线保护。每票照常跑能运行的检查,仅对计划逐项声明的命令/范围/原因延后整体绿,保存真实 exit 与待验状态。意外局部失败阻塞,环境/编译错误不能当行为红。没有足够保护或无法获得行为红时回到设计,不把该票改名“重构”来跳过。
组员的公共行为最终在唯一组验证票上检验,普通功能票仍保持五步和有效红绿。没有有效红的纯重构与组员/组验证票不派给普通 implementer,不改变 implementation-result phase 或原接收条件。组验证完成不替代最终特性审查、相关回归和收尾全量。
收尾纯重构
先核实授权范围及公共行为不变,再用已有行为测试记录调整前后的通过证据;缺保护时,先依据现行契约补行为刻画并观察当前实现通过,再结构调整。刻画通过不是红证据;若当前行为与需求不同,转缺陷复现红绿路径。
计划中的前置纯重构与组迁移按已批准时机执行,额外候选由现有收尾审查接收,收尾修复后复审受影响维度;quick-fix 使用自身收尾,不强制开启可选 acceptance-qa。处置沿已有授权边界。implementer 无法提供有效红证据的纯重构票回报 blocked,由主线程按现有串行分流处理,不改结果 schema 或普通实施票的红绿条件。
为什么顺序重要(借口对照表)
| 借口 | 现实 |
|---|---|
| "太简单不用测" | 简单代码也会坏。测试 30 秒的事。 |
| "我写完再补测试" | 事后测试立即通过,什么都证明不了——可能测错对象、测实现不测行为、漏掉你忘了的边缘情况。 |
| "事后测试达到同样目的" | 事后测试回答"这代码做了什么";事前测试回答"这代码该做什么"。事后测试被实现偏置——你测你建的,不是需求要的。 |
| "我已经手工测过了" | 手工测试无记录、不可重跑、高压下必漏。"我试了没问题" ≠ 系统性验证。 |
| "删掉 X 小时的工作太浪费" | 沉没成本谬误。留着没有真实测试的代码才是技术债。 |
| "留着当参考,先写测试" | 你会照着改。那就是事后测试。删除就是删除。 |
| "需要先探索" | 可以。探索完扔掉,从 TDD 重新开始。 |
| "测试难写 = 该 mock 更多" | 测试难写 = 设计有问题。难测即难用。 |
| "TDD 教条,我要务实" | TDD 就是务实:提交前抓 bug 比事后调试快,回归即刻暴露,测试即文档,重构有保护网。"务实的捷径" = 在生产环境调试 = 更慢。 |
Red Flags —— 停下来重来
- 新增/改变行为的代码先于有效失败测试;缺陷测试未复现就立即通过
- 说不清测试为什么失败
- "就这一次"、"我已经手工测过了"、"事后测试目的一样"、"重要的是精神不是仪式"
- "留作参考"、"改造现有代码"、"已经花了 X 小时删了可惜"
- "TDD 教条,我在务实"、"这次情况特殊因为……"
核实违反红绿纪律后,删除本次违规行为实现并重做。 纯重构刻画和已授权例外不误判为违规。
完成前检查清单
- 获批 seam 的公共行为、相关 Scenario、边缘与错误路径已有覆盖,不按新增函数数量要求直测
- 新增/改变行为的测试观察到有效红;纯重构另有前后绿,刻画不冒充红
- 红证据源于行为缺失,编译/环境故障、零测试、SKIP 不当 PASS
- 行为红绿使用最小实现;纯重构保持已有契约
- 目标及相关测试通过,计划最终全量和范围外失败裁决保持
- 输出干净(无报错、无警告)
- 测试验证真实行为,mock 遵循 testing-anti-patterns 的准入与声明依赖边界
- 边缘情况与错误路径已覆盖
按适用路径核对,不把其他路径的证据冒充自己的完成依据;例外有授权来源,重构候选已交收尾。
调试集成
发现 bug?先写复现它的失败测试,再走 TDD 循环。测试既证明修复又防止回归。永远不要不带测试修 bug。
Mock 与测试反模式
添加 mock、测试工具方法、或想给生产类加测试专用方法时,先读 testing-anti-patterns.md:不测 mock 的行为、不给生产类加测试专用方法、不在不理解依赖链时 mock、mock 必须镜像真实结构的完整字段。
最终规则
新增或改变行为 → 有效红 → 最小实现 → 绿
纯重构 → 行为保护 → 结构调整 → 保持绿
例外 → 唯一清单及可追溯授权;普通实施票的红绿条件不变
未经用户允许,没有例外。