Skill Learning

tdd

1. Bilingual SKILL.md

左右滑动可直接切换英文版与中文版;两种语言按段落自动对齐。英文中的蓝色虚线词语可点击查看解释。共标记 12 处。

入口 skill:tdd左右滑动切换语言 · 段落位置自动对齐
# Test-Driven Development
TDD is the . This skill is the reference that makes that loop produce : what a good test is, where tests go, the anti-patterns, and the rules of the loop. Every section applies on every cycle: consult them before and during the loop, not after.
When exploring the codebase, read `CONTEXT.md` (if it exists) so test names and interface vocabulary match the project's domain language, and respect ADRs in the area you're touching.
## What a good test is
Tests verify behavior through , not implementation details. Code can change entirely; tests shouldn't. A good test : "user can checkout with valid cart" tells you exactly what capability exists, and it because it doesn't care about internal structure.
See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.
## Seams: where tests go
A **seam** is the public boundary you test at: the interface where you observe behavior without reaching inside. Tests live at seams, never against internals.
**Test only at .** Before writing any test, write down the seams under test and confirm them with the user. No test is written at an unconfirmed seam. You can't test everything, so agreeing the seams up front is how testing effort and complex logic instead of every edge case.
Ask: "What's the public interface, and which seams should we test?"
When the shape of that interface is itself in question (how deep the module is, where the seam belongs, what the interface should expose), call the Skill tool with "codebase-design" for the vocabulary. It is the shared source of the module, interface, depth, seam, adapter, leverage and locality terms, and it is a reference to consult, not a session to run.
## Anti-patterns
- **Implementation-coupled**: mocks internal collaborators, tests private methods, or verifies through a side channel (querying the database instead of using the interface). The tell: the test breaks when you refactor but behavior hasn't changed. - **Tautological**: the assertion recomputes the expected value the way the code does (`expect(add(a, b)).toBe(a + b)`, a snapshot derived by hand the same way, a constant asserted equal to itself), so it and can never disagree with the code. Expected values must come from an independent source of truth: a known-good literal, a worked example, the spec. - **Horizontal slicing**: writing all tests first, then all implementation. Bulk tests verify _imagined_ behavior: you test the _shape_ of things rather than user-facing behavior, the tests go insensitive to real changes, and you commit to test structure before understanding the implementation. Work in **** instead: one test → one implementation → repeat, each test a **** that responds to what the last cycle taught you.
## Rules of the loop
- **** Write the failing test first, then only enough code to pass it. Don't anticipate future tests or add . - **One slice at a time.** One seam, one test, one minimal implementation per cycle. - **Refactoring is not part of the loop.** It belongs to the review stage (see the `code-review` skill), not the red → green implementation cycle.
# 测试驱动开发
TDD 就是红灯 → 绿灯循环。这个技能是一份参考指南,让该循环产出值得长期保留的测试:什么是好测试、测试放在哪里、有哪些反模式,以及循环规则。每个小节都适用于每一轮循环:在循环之前和期间查阅,而不是事后再看。
探索代码库时,请读取 `CONTEXT.md`(如果存在),让测试名称和接口词汇与项目的领域语言一致,并遵守当前改动区域内的 ADR。
## 什么是好测试
测试应通过公共接口验证行为,而不是验证实现细节。代码可以完全改变,测试不应该随之失效。好测试读起来像规格说明:“user can checkout with valid cart” 准确告诉你系统具备什么能力;它不关心内部结构,因此能经受重构。
示例参见 [tests.md](tests.md),mock 指南参见 [mocking.md](mocking.md)。
## Seam:测试放在哪里
**seam(接缝/边界)** 是你进行测试的公共边界:你可以在这里观察行为,而无需伸入内部。测试应位于 seam 上,绝不能针对内部实现。
**只在预先约定的 seam 上测试。** 写任何测试前,先写下要测试的 seam 并与用户确认。未确认的 seam 上不得写测试。你无法测试一切,因此预先约定 seam,才能让测试投入落在关键路径和复杂逻辑上,而不是覆盖每个边缘情况。
询问:“公共接口是什么?我们应该测试哪些 seam?”
当接口的形态本身仍有疑问时(模块应有多深、seam 应位于哪里、接口应该暴露什么),请用 “codebase-design” 调用 Skill tool 获取词汇。它是 module、interface、depth、seam、adapter、leverage 和 locality 等术语的共享来源;它是一份供查阅的参考,而不是要运行的会话。
## 反模式
- **与实现耦合(Implementation-coupled)**:mock 内部协作者、测试私有方法,或通过旁路验证(例如查询数据库而不是使用接口)。识别信号:当行为没有改变、仅做重构时测试却失败。 - **同义反复(Tautological)**:断言用与代码相同的方式重新计算期望值(例如 `expect(add(a, b)).toBe(a + b)`、手工按同一方式生成快照,或断言常量等于其自身),所以它从构造上就注定通过,永远无法与代码产生分歧。期望值必须来自独立真值来源:已知正确的字面值、推导示例或规格。 - **横向切片(Horizontal slicing)**:先写完所有测试,再写所有实现。批量测试验证的是_想象中的_行为:你测的是事物的_形状_而非面向用户的行为,测试对真实变化变得不敏感,而且你会在理解实现前就承诺测试结构。应改用**纵向切片(vertical slices)**:一条测试 → 一个实现 → 重复;每条测试都是一发 **tracer bullet(曳光弹)**,会响应上一轮学到的内容。
## 循环规则
- **先红后绿。** 先写失败测试,再只写足够让它通过的代码。不要预测未来的测试,也不要加入猜测性的功能。 - **每次一个切片。** 每轮只处理一个 seam、一条测试和一个最小实现。 - **重构不属于这个循环。** 它属于 review 阶段(参见 `code-review` 技能),而不是红灯 → 绿灯的实现循环。

Entry SHA-256: cb01f66bebfaa25fa1f88e6b7e769cd9fd9f35b1120b8563749820738814c927

2. Why This Skill Is Clever

一句话核心机制

把 TDD 压缩为受约束的“一个已确认边界、一个行为测试、一个最小实现”反馈循环,并用独立真值、公共接口和纵向切片保证测试长期有价值。

触发与调用边界

直接来源: frontmatter 把触发范围限定为用户明确要求 test-first、提到 “red-green-refactor”,或需要 integration tests 的场景。正文又要求每次循环前后都持续参考本技能,而不是完成实现后才补看规则。

直接来源: 写任何测试前必须先写下测试 seam 并与用户确认;若公共接口本身尚未确定,技能只调用 codebase-design 作为词汇参考,而不是启动另一个完整工作流。

解释: 这使技能适合已经准备进入实现、但需要先确定“从哪里观察行为”的任务。若用户尚未决定功能目标,或当前工作只是探索方案,应该先完成需求与接口设计,而不是立刻写测试。

信息架构 / 执行顺序

  1. 读取领域语境: 若存在 CONTEXT.md,用其中的领域语言命名测试,并尊重相关 ADR。
  2. 定义好测试: 只通过公共接口验证行为,让测试像规格说明并能经受内部重构。
  3. 确认 seam: 写下候选公共边界,与用户确认测试投入应落在哪些关键路径。
  4. 排除反模式: 检查实现耦合、同义反复和横向批量切片。
  5. 执行单轮循环: 先写一个失败测试,再写恰好足够的实现使其通过。
  6. 吸收反馈并重复: 把上一轮测试当作 tracer bullet,用新理解决定下一条纵向切片。
  7. 推迟重构: 当前技能只负责 red → green;重构进入后续 code-review 阶段。

最巧妙的设计点

1. 先确认观察边界,再允许产生测试

“Test only at pre-agreed seams.”

直接来源: seam 不是执行者私下选择的实现细节,而是写测试前必须与用户确认的公共观察点。这样把“测什么”从低层编码动作提升为资源分配决策:有限测试投入优先落在关键路径和复杂逻辑。

2. 用“独立真值”识别看似合理的空测试

“Expected values must come from an independent source of truth”

直接来源: 技能不仅防止常见的私有方法测试,还单独定义 tautological test:如果期望值用与实现相同的方式重新计算,测试就会 pass by construction。已知字面值、推导示例或规格才是能与实现发生分歧的真值来源。

3. 把每个循环做成学习探针,而不是批量承诺

“each test a tracer bullet”

直接来源: 纵向切片要求“一条测试 → 一个实现 → 重复”,下一轮必须响应上一轮学到的事实。这避免先写完大量针对“想象行为”的测试,再被迫让实现迁就早期猜测。

4. 对最小实现设置明确的负面边界

“Don't anticipate future tests or add speculative features.”

直接来源: “只写够通过的代码”容易被宽松解释;这条规则进一步禁止预测未来测试和顺手加入猜测功能,使 green 阶段保持可审计的最小范围。

5. 把重构从循环中显式移出

“Refactoring is not part of the loop.”

直接来源: 与经典 red-green-refactor 口号不同,本技能明确把 refactoring 交给 code-review。它用阶段所有权避免在行为尚未逐片验证时同时改变结构,减少一个循环内的变量数量。

解释: 这是有意的流程取舍,而不是 TDD 的普遍定义;迁移到其他团队时必须确认其 review 阶段真的会承担重构,否则技术债会被持续推迟。

6. 让参考资料细化原则,但不污染主循环

“See tests.md for examples and mocking.md for mocking guidelines.”

支持性上下文: tests.md 用好坏示例具体化“WHAT not HOW”和独立字面真值;mocking.md 把 mock 限制到系统边界,并推荐依赖注入和具体 SDK 风格接口。主 SKILL.md 保持短小,执行者只在需要例子或 mock 决策时下钻。

防失败机制与显式不变量

直接来源:

取舍、可移植风险与宿主假设

解释: 强制用户逐个确认 seam 能提升测试投资质量,但在无人值守、批量修复或已有成熟测试策略的仓库中会增加交互成本。“公共接口”也可能过粗:只做端到端测试会降低故障定位速度,因此团队仍需定义哪些模块边界算作稳定 seam。技能假设宿主能读取 CONTEXT.md、ADR,并能通过 Skill tool 找到 codebase-design 与 code-review;换到不支持这些约定的 harness 时需要映射替代入口。最后,把 refactor 延后能保持循环纯度,却依赖 review 阶段可靠发生,否则最小实现会累积成结构债务。

可迁移原则

  1. 在产生工件前确认观察边界。 先约定从哪里判断成功,再决定具体测试。
  2. 要求验证证据独立于被验证算法。 任何与实现同源的期望值都可能制造假安全感。
  3. 用完整的微小纵向切片换取快速反馈。 每轮贯穿测试和实现,不提前批量承诺结构。
  4. 为“最小”写出禁止清单。 明确禁止预测未来和附带功能,比只说“少写代码”更可执行。
  5. 为每个阶段指定唯一所有者。 若重构不在实现循环,就必须明确由哪个后续阶段接管。

今日实践题

假设你要为“用户可以用有效购物车结账”写第一条测试:你会先与用户确认哪个公共 seam、采用什么独立期望值,并把哪些内部调用细节明确排除在断言之外?

Evidence note

本文关于触发条件、公共接口、seam 确认、三类反模式、纵向切片和 red → green 规则的结论来自所选 SKILL.md。tests.md 与 mocking.md 只作为支持性上下文,用于解释示例和 mock 边界,未作为所选原文展示。关于交互成本、测试粒度、harness 依赖和重构延后风险属于设计解释。

Source & provenance
Repository path
skills/engineering/tdd/SKILL.md
Generated
2026-08-22 08:22 Asia/Shanghai
Upstream commit
5b15a47f2d7150f545fbcacbfe381787fc0230dc
Source
View on GitHub