# Tdd

> Test-driven development. Use when building a feature or fixing a bug test-first, when the user mentions "red-green-refactor", "TDD", or asks for tests before code. Enforces the iron law "no production code without a failing test first" with a red-green-refactor loop, anti-patterns, and cross-framework guidance.

- Skill: `carolz1/tdd` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add carolz1/tdd`
- Raw SKILL.md: https://api.skillmd.com/api/skills/carolz1/tdd/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: CarolZ1 (https://skillmd.com/u/carolz1)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/carolz1/tdd

---


# Test-Driven Development / 测试驱动开发

A red → green → refactor loop discipline that keeps tests worth keeping. / 一个能让测试经得起时间考验的红 → 绿 → 重构循环纪律。

## When to use / 何时使用

- Building any new feature (component, function, API endpoint, script) / 构建任何新功能
- Fixing any bug (write a failing regression test before fixing) / 修复任何 Bug
- Refactoring any code that lacks coverage / 重构任何测试覆盖率不足的代码
- User mentions "red-green-refactor", "TDD", "test-first", "先写测试", "测试驱动" / 用户提到"red-green-refactor"、"TDD"、"先写测试"、"测试驱动"

Do NOT use when / 不适用:
- Pure exploratory spikes (discard the code after) / 纯探索性原型（之后会丢弃代码）
- One-shot scripts with no callers / 一次性脚本（没有调用者）

## The Iron Law / 铁律

```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.
没有失败的测试，就不能写生产代码。
```

If you wrote code first, delete it and start over. "I'll write the test after" is the rationalization that defeats TDD. / 如果你先写了代码，删掉重写。"之后补测试"是击败 TDD 的合理化借口。

## The Loop / 循环

```
┌──────────────────────────────────────────┐
│  RED   Write a failing test              │
│  红    写一个会失败的测试                 │
│         ↓                                │
│  RUN   Confirm it fails for the right   │
│  跑    reason (not a typo/setup error)   │
│        确认失败原因是「业务缺失」         │
│        而不是「拼写/环境」错误            │
│         ↓                                │
│  GREEN Write the minimum code to pass    │
│  绿    写最少代码让测试通过              │
│         ↓                                │
│  RUN   Confirm it passes                 │
│  跑    确认通过                           │
│         ↓                                │
│  REFACTOR Clean up (test + code together)│
│  重构  一起清理（测试和代码同时）        │
│         ↓                                │
│  COMMIT Small, focused commit            │
│  提交  小而聚焦的提交                     │
│         ↓                                │
│       Next slice / 下一个切片            │
└──────────────────────────────────────────┘
```

One slice at a time. One seam, one test, one minimal implementation per cycle. / 一次一个切片。每个循环一个边界、一个测试、最小实现。

## 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. / **边界（seam）**是你测试的公共边界——你观察行为但不深入内部的接口。测试只在边界，绝不在内部。

**Test only at pre-agreed seams.** Before writing any test, write down the seams under test and confirm them. You can't test everything, so agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case. / **只在预先约定的边界测试**。写任何测试之前，先列出要测的边界并确认。你不可能测所有，所以预先约定边界能让测试力量集中到关键路径和复杂逻辑上，而不是每个边界条件。

Ask first: "What's the public interface, and which seams should we test?" / 先问：「公共接口是什么？应该测哪些边界？」

## 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). Expected values must come from an independent source of truth: a known-good literal, a worked example, the spec. / 断言用代码同样的方式重算期望值（`expect(add(a, b)).toBe(a + b)`、手工算的快照、常量断言等于自身）。期望值必须来自独立的真相源：已知的字面值、算好的示例、规范。

- **Horizontal slicing / 横向切片**: writing all tests first, then all implementation. Bulk tests verify _imagined_ behavior; tests go insensitive to real changes; you commit to test structure before understanding the implementation. Work in **vertical slices** instead: one test → one implementation → repeat, each test a **tracer bullet** that responds to what the last cycle taught you. / 先写所有测试，再写所有实现。批量测试验证的是「想象中的」行为；测试对真实变化不敏感；你在理解实现前就锁死了测试结构。改用**纵向切片**：一个测试 → 一个实现 → 重复，每个测试都是回应上次循环教你的**示踪弹**。

- **Speculative features / 投机功能**: Don't anticipate future tests or add features "while I'm here." YAGNI. If you didn't write a failing test for it, don't build it. / 不要预想未来测试或「顺便」加功能。YAGNI（你不会需要它）。没写失败测试，就不要做。

See [references/anti-patterns.md](references/anti-patterns.md) for full list with BAD/GOOD examples. / 完整反模式列表与正反例见 [references/anti-patterns.md](references/anti-patterns.md)。

## Testing frameworks / 测试框架

Works with any test runner. Pick one based on your stack: / 适用于任何测试运行器。按技术栈选择：

| Stack / 技术栈 | Test runner | Example assertion |
|---|---|---|
| JavaScript / TypeScript (Node) | Jest, Vitest, Mocha | `expect(fn(x)).toBe(y)` |
| Python | pytest, unittest | `assert fn(x) == y` |
| Go | built-in `go test` | `if got := fn(x); got != y { t.Errorf(...) }` |
| Rust | built-in `cargo test` | `assert_eq!(fn(x), y)` |
| Java / Kotlin | JUnit 5, Kotest | `assertEquals(y, fn(x))` |
| C# | xUnit, NUnit | `Assert.Equal(y, fn(x))` |
| Ruby | RSpec, Minitest | `expect(fn(x)).to eq(y)` |

If your project has no test framework yet, see [examples/no-framework.md](examples/no-framework.md) for the manual approach (use assert + a runner script). / 如果项目还没有测试框架，手测方法见 [examples/no-framework.md](examples/no-framework.md)（用 assert + 临时运行脚本）。

## Rules of the loop / 循环纪律

1. **Red before green.** Failing test first, then only enough code to pass. / 先红后绿。先写失败测试，再写最少代码让它通过。
2. **One slice at a time.** One seam, one test, one minimal implementation. / 一次一个切片。
3. **Refactoring is part of the loop, not separate.** After green, clean up both the test and the code while tests still pass. Don't bolt on extra refactors. / 重构是循环的一部分，不是单独的环节。绿之后，趁测试还过，清理测试和代码。不要额外加重构。
4. **Don't refactor + add features in the same cycle.** Finish the slice, commit, then start the next one. / 不要在同一循环里既重构又加功能。完成切片、提交、再开下一个。
5. **Commit small.** Each red-green cycle (or every 2-3 cycles) is a commit. / 小步提交。每个红绿循环（或每 2-3 个循环）一次提交。

## Mocking / Mocking 原则

Mock at the seam, not inside the unit. If you find yourself mocking private methods, you're testing the wrong thing. / 在边界 mock，不要在单元内部 mock。如果你发现自己在 mock 私有方法，说明你测错了对象。

See [references/mocking.md](references/mocking.md) for: when to mock, when not to mock, dependency injection patterns, fake vs. stub vs. mock distinction. / 何时 mock、何时不 mock、依赖注入模式、fake/stub/mock 区别见 [references/mocking.md](references/mocking.md)。

## Worked examples / 完整示例

See / 见：
- [examples/ts-red-green-refactor.md](examples/ts-red-green-refactor.md) — TypeScript + Vitest, full red→green→refactor on a `slugify` function / TypeScript + Vitest，slugify 函数完整红→绿→重构
- [examples/py-bug-fix.md](examples/py-bug-fix.md) — Python + pytest, fixing an off-by-one bug with a regression test / Python + pytest，用回归测试修 off-by-one Bug

## Output expectations / 输出期望

When you finish a TDD cycle, you have: / 完成 TDD 循环后，你拥有：

- ✅ A failing test that now passes / 现在通过的失败测试
- ✅ Minimum implementation to satisfy it / 满足它的最小实现
- ✅ Test name that reads like a specification (e.g. `user can checkout with empty cart shows error`) / 测试名像规范一样读
- ✅ A small commit / 小提交

You do NOT have: / 你**没有**：

- ❌ Speculative features / 投机功能
- ❌ Mocks of internal collaborators / 内部协作者的 mock
- ❌ Tautological assertions / 恒真断言
- ❌ Tests that break on every refactor / 每次重构就挂的测试

## Red flags — STOP / 红旗 — 停止

If you find yourself: / 如果你发现自己在：

- Writing implementation before the test / 先写实现再写测试
- Adding a feature "while I'm here" / 「顺便」加功能
- Mocking private methods or internal collaborators / mock 私有方法或内部协作者
- Asserting `expect(add(a,b)).toBe(a+b)` / 写 `expect(add(a,b)).toBe(a+b)` 这类恒真断言
- Writing all tests first, then all implementation / 先写所有测试，再写所有实现
- Skipping the run-after-red (no proof the test failed for the right reason) / 跳过红之后的运行（没有证据证明测试因正确原因失败）

→ **STOP. Return to RED.** / **停止。回到红。**

## Verification before claiming done / 声称完成前的验证

Before saying "the TDD cycle is complete": / 在说「TDD 循环完成」之前：

- [ ] The test that was red is now green / 之前红的测试现在绿了
- [ ] No other tests broke / 其他测试没挂
- [ ] The test name describes behavior, not implementation / 测试名描述行为而非实现
- [ ] The test would catch a regression if the implementation broke / 实现坏了测试能抓到
- [ ] The diff is small (ideally <50 lines) / 差异小（理想 <50 行）

If any item is unchecked, the cycle isn't done. / 任何一项未勾，循环未完成。
