测试驱动开发
- 作者仓库星标 229,270
- 叉子 20,393
- 作者更新于 2026年6月16日 05:34
- 作者仓库 superpowers
- 领域
- 工程开发
- 兼容 Agent
-
- Claude Code
- Cursor
- Cline
- Codex
- Windsurf
- Gemini CLI
- +20
- 信任分
- 88 / 100 · 社区维护
- 作者 / 版本 / 许可
- @obra · 未声明 license
- Token 消耗评级
- 低消耗
- 接入复杂程度
- 即装即用
- 是否需要外部 API Key
- 不需要
- 兼容的系统
- macOS · Linux · Windows
- 底层运行要求
- 无特殊要求
- 文件与系统权限
-
- 只读
- 允许写入 / 修改
- 网络行为
- 仅限本地
- 安装命令数
- 26 条
档案由构建时根据 SKILL.md 与安装命令自动衍生,可能与作者实际意图存在差异。
需要注意: 未限定 allowed-tools,默认拥有全部工具权限。
---
name: test-driven-development
description: Use when implementing any feature or bugfix, before writing implementation code Write the test f…
category: 工程开发
runtime: 无特殊运行时
---
# test-driven-development 输出预览
## PART A: 任务判断
- 适用问题:代码实现、重构、调试或代码审查。
- 输入要求:目标材料、限制条件、期望输出和验收方式。
- 证据边界:围绕“Overview / When to Use / The Iron Law”读取原文规则,不把推断写成作者承诺。
## PART B: 执行结果
- **01** 任务判断:确认你的需求是否属于代码实现、重构、调试或代码审查,并标出输入、限制和预期结果。
- **02** 执行计划:优先按“Overview / When to Use / The Iron Law”拆成步骤,说明每一步会读取什么、修改什么、产出什么。
- **03** 交付结果:给出可复制的命令、文件改动、检查清单或内容草稿,并说明如何继续迭代。
- **04** 风险边界:结合 读取文件、写入/修改文件、主要在本地完成、通常不需要额外 API Key 给出执行前确认项。
## Running Rules
- 读取文件、写入/修改文件;主要在本地完成;通常不需要额外 API Key。
- 先小样例验证,再放大到真实任务。
- 交付时同时给结果、检查口径和下一步迭代建议。 原文没有稳定的斜杠命令要求。安装验证后通常全局生效,直接在对话里点名这个 Skill 并描述任务即可。
告诉 Agent 目标文件或材料、期望结果、不可改范围、是否允许联网或执行命令。本 Skill 的权限画像是:读取文件、写入/修改文件。
先用一个小任务确认它会围绕“Overview / When to Use / The Iron Law”工作;涉及文件或命令时,先看 diff、日志、预览或测试结果。
检查最终产物是否包含明确结果、必要证据和下一步动作;如果输出泛泛而谈,就补充输入、边界和验收标准后重跑。
---
name: test-driven-development
description: Use when implementing any feature or bugfix, before writing implementation code Write the test f…
category: 工程开发
source: obra/superpowers
---
# test-driven-development
## 什么时候使用
- 把测试当成设计工具,而不是写完才补的检查项 适合处理工程开发场景下的代码实现、调试、重构、测试或代码审查,核心价值是把输入、判断、执行、验证和交付边界固定下来,避免 Agent 泛泛回答。 把任务拆成可执行、可检查、可继续迭代的步骤;通…
- 面向代码实现、重构、调试或代码审查,优先处理能明确输入、步骤和验收标准的工作。
## 需要提供什么
- 目标材料、目录范围、期望结果和不可改动内容。
- 是否允许联网、执行命令、读写文件或调用外部服务。
## 执行规则
- 围绕「Overview / When to Use / The Iron Law」组织步骤,不把推断写成作者事实。
- 读取文件、写入/修改文件;主要在本地完成;通常不需要额外 API Key。
- 先跑小样例,确认结果可检查后再扩大任务范围。
## 输出要求
- 给出最终产物、关键证据、验证方式和下一步动作。
- 信息不足时标记 unknown,不编造命令、平台或依赖。 作者原文负责流程事实;仓库文件负责来源和命令;流狐只补充适用场景、限制和质量判断。
skill "test-driven-development" {
输入层 -> 用户目标 + 目标文件 + 禁止范围 + 验收标准
上下文层 -> Overview / When to Use / The Iron Law
规则层 -> SKILL.md 触发条件 / 执行顺序 / 输出格式
运行层 -> 无特殊运行时 | 读取文件、写入/修改文件 | 主要在本地完成
安全层 -> 通常不需要额外 API Key + 小任务验证 + diff / 日志复核
输出层 -> 可复制结果 + 检查清单 + 下一步迭代
} 测试驱动开发 (TDD)
概述
先写测试。看着它失败。然后编写最少的代码使其通过。
核心原则: 如果你没有看到测试失败,你就不知道它是否测试了正确的东西。
违反规则的字面意思就是违反规则的精神。
何时使用
始终:
- 新功能
- 错误修复
- 重构
- 行为变更
例外情况(请咨询你的搭档):
- 一次性原型
- 生成的代码
- 配置文件
想着「这次就跳过 TDD 吧」?停下。那是自圆其说。
铁律
没有先失败的测试,就没有生产代码
在测试之前写代码?删除它。重新开始。
没有例外:
- 不要保留它作为「参考」
- 不要边写测试边「调整」它
- 不要看它
- 删除就是删除
从测试开始全新实现。就这样。
红-绿-重构
digraph tdd_cycle {
rankdir=LR;
red [label="RED\n编写失败测试", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="验证失败\n是否正确", shape=diamond];
green [label="GREEN\n最少代码", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="验证通过\n全部绿色", shape=diamond];
refactor [label="REFACTOR\n清理", shape=box, style=filled, fillcolor="#ccccff"];
next [label="下一步", shape=ellipse];
red -> verify_red;
verify_red -> green [label="是"];
verify_red -> red [label="错误\n失败"];
green -> verify_green;
verify_green -> refactor [label="是"];
verify_green -> green [label="否"];
refactor -> verify_green [label="保持\n绿色"];
verify_green -> next;
next -> red;
}
红 (RED) - 编写失败测试
编写一个最简单的测试,展示应该发生什么。
const result = await retryOperation(operation);
expect(result).toBe('success'); expect(attempts).toBe(3); });
名称清晰,测试真实行为,只做一件事
</Good>
<Bad>
```typescript
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
名称模糊,测试的是 mock 而非代码
要求:
- 一个行为
- 清晰的名称
- 真实代码(除非无法避免,否则不使用 mock)
验证红 (Verify RED) - 观察它失败
强制。绝不能跳过。
npm test path/to/test.test.ts
确认:
- 测试失败(不是报错)
- 失败信息符合预期
- 失败是因为缺少功能(不是拼写错误)
测试通过? 你在测试现有行为。修复测试。
测试报错? 修复错误,重新运行直到它正确失败。
绿 (GREEN) - 最少代码
编写最简单的代码来通过测试。
不要添加功能、重构其他代码或「改进」超出测试范围。
验证绿 (Verify GREEN) - 观察它通过
强制。
npm test path/to/test.test.ts
确认:
- 测试通过
- 其他测试仍然通过
- 输出干净(无错误、无警告)
测试失败? 修复代码,而不是测试。
其他测试失败? 立即修复。
重构 (REFACTOR) - 清理
仅在通过测试后:
- 消除重复
- 改进命名
- 提取辅助函数
保持测试绿色。不要添加行为。
重复
为下一个功能编写下一个失败测试。
好的测试
| 质量 | 好 | 坏 |
|---|---|---|
| 最少 | 只做一件事。名称中有「and」?拆分它。 | test('validates email and domain and whitespace') |
| 清晰 | 名称描述行为 | test('test1') |
| 展示意图 | 演示期望的 API | 模糊代码应该做什么 |
为什么顺序很重要
「我会在之后写测试来验证它是否有效」
在代码之后编写的测试会立即通过。立即通过并不能证明任何事情:
- 可能测试了错误的东西
- 可能测试了实现,而不是行为
- 可能遗漏了你忘记的边缘情况
- 你从未看到它捕获到 bug
测试优先迫使你看到测试失败,从而证明它确实测试了某些东西。
「我已经手动测试了所有边缘情况」
手动测试是临时的。你认为你测试了所有东西,但是:
- 没有你测试过的记录
- 当代码更改时无法重新运行
- 在压力下容易忘记情况
- 「我试过它有效」≠ 全面
自动化测试是系统的。它们每次都以相同的方式运行。
「删除 X 小时的工作是浪费」
沉没成本谬误。时间已经过去了。你现在的选择:
- 删除并用 TDD 重写(再花 X 小时,高度自信)
- 保留它并在之后添加测试(30 分钟,低自信,可能存在 bug)
真正的「浪费」是保留你无法信任的代码。没有真实测试的工作代码是技术债务。
「TDD 是教条主义的,务实意味着适应」
TDD 就是务实的:
- 在提交前发现 bug(比之后调试更快)
- 防止回归(测试立即捕获中断)
- 记录行为(测试展示如何使用代码)
- 启用重构(自由更改,测试捕获中断)
「务实」的捷径 = 在生产中调试 = 更慢。
「之后写测试也能达到同样的目标——这是精神而非仪式」
不。之后写测试回答的是「这做了什么?」先写测试回答的是「这应该做什么?」
之后写测试会受到你实现的影响。你测试的是你构建的东西,而不是所需的东西。你验证的是你记住的边缘情况,而不是发现的边缘情况。
先写测试迫使你在实现之前发现边缘情况。之后写测试验证你是否记住了所有东西(你没有)。
30 分钟的之后写测试 ≠ TDD。你获得了覆盖率,但失去了测试有效的证明。
常见借口
| 借口 | 现实 |
|---|---|
| 「太简单了,不需要测试」 | 简单的代码也会出错。测试只需 30 秒。 |
| 「我之后再测试」 | 立即通过的测试什么也证明不了。 |
| 「之后写测试也能达到同样的目标」 | 之后写测试 = 「这做了什么?」先写测试 = 「这应该做什么?」 |
| 「已经手动测试过了」 | 临时 ≠ 系统。没有记录,无法重新运行。 |
| 「删除 X 小时的工作是浪费」 | 沉没成本谬误。保留未经验证的代码是技术债务。 |
| 「保留作参考,先写测试」 | 你会调整它。那就是之后写测试。删除就是删除。 |
| 「需要先探索一下」 | 没问题。扔掉探索代码,从 TDD 开始。 |
| 「测试困难 = 设计不清晰」 | 听从测试。难以测试 = 难以使用。 |
| 「TDD 会拖慢我」 | TDD 比调试更快。务实 = 测试优先。 |
| 「手动测试更快」 | 手动测试不能证明边缘情况。每次更改你都得重新测试。 |
| 「现有代码没有测试」 | 你正在改进它。为现有代码添加测试。 |
危险信号 - 停止并重新开始
- 代码在测试之前
- 测试在实现之后
- 测试立即通过
- 无法解释测试为何失败
- 「稍后」添加测试
- 为「就这一次」找借口
- 「我已经手动测试过了」
- 「之后写测试也能达到同样的目的」
- 「这是关于精神而非仪式」
- 「保留作参考」或「调整现有代码」
- 「已经花了 X 小时,删除是浪费」
- 「TDD 是教条主义的,我只是务实」
- 「这不同,因为...」
所有这些都意味着:删除代码。用 TDD 重新开始。
示例:Bug 修复
Bug: 接受空电子邮件
红 (RED)
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
验证红 (Verify RED)
$ npm test
FAIL: expected 'Email required', got undefined
绿 (GREEN)
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
验证绿 (Verify GREEN)
$ npm test
PASS
重构 (REFACTOR) 如果需要,为多个字段提取验证逻辑。
验证清单
在将工作标记为完成之前:
- 每个新函数/方法都有一个测试
- 在实现之前观察到每个测试都失败了
- 每个测试都因预期原因(缺少功能,而非拼写错误)而失败
- 编写了最少的代码来通过每个测试
- 所有测试都通过
- 输出干净(无错误、无警告)
- 测试使用真实代码(仅在无法避免时使用 mock)
- 覆盖了边缘情况和错误
无法勾选所有复选框?你跳过了 TDD。重新开始。
遇到困难时
| 问题 | 解决方案 |
|---|---|
| 不知道如何测试 | 编写期望的 API。先写断言。咨询你的搭档。 |
| 测试过于复杂 | 设计过于复杂。简化接口。 |
| 必须 mock 所有东西 | 代码耦合度太高。使用依赖注入。 |
| 测试设置庞大 | 提取辅助函数。仍然复杂?简化设计。 |
调试集成
发现 bug?编写一个失败测试来重现它。遵循 TDD 循环。测试证明修复并防止回归。
绝不要在没有测试的情况下修复 bug。
测试反模式
在添加 mock 或测试工具时,请阅读 @testing-anti-patterns.md 以避免常见陷阱:
- 测试 mock 行为而不是真实行为
- 在生产类中添加仅用于测试的方法
- 在不理解依赖关系的情况下进行 mock
最终规则
生产代码 → 测试存在且先失败
否则 → 不是 TDD
未经你的搭档许可,没有例外。
Test-Driven Development (TDD)
Overview
Write the test first. Watch it fail. Write minimal code to pass.
Core principle: If you didn't watch the test fail, you don't know if it tests the right thing.
Violating the letter of the rules is violating the spirit of the rules.
When to Use
Always:
- New features
- Bug fixes
- Refactoring
- Behavior changes
Exceptions (ask your human partner):
- Throwaway prototypes
- Generated code
- Configuration files
Thinking "skip TDD just this once"? Stop. That's rationalization.
The Iron Law
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write code before the test? Delete it. Start over.
No exceptions:
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete
Implement fresh from tests. Period.
Red-Green-Refactor
digraph tdd_cycle {
rankdir=LR;
red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="Verify fails\ncorrectly", shape=diamond];
green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="Verify passes\nAll green", shape=diamond];
refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
next [label="Next", shape=ellipse];
red -> verify_red;
verify_red -> green [label="yes"];
verify_red -> red [label="wrong\nfailure"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no"];
refactor -> verify_green [label="stay\ngreen"];
verify_green -> next;
next -> red;
}
RED - Write Failing Test
Write one minimal test showing what should happen.
const result = await retryOperation(operation);
expect(result).toBe('success'); expect(attempts).toBe(3); });
Clear name, tests real behavior, one thing
</Good>
<Bad>
```typescript
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
Vague name, tests mock not code
Requirements:
- One behavior
- Clear name
- Real code (no mocks unless unavoidable)
Verify RED - Watch It Fail
MANDATORY. Never skip.
npm test path/to/test.test.ts
Confirm:
- Test fails (not errors)
- Failure message is expected
- Fails because feature missing (not typos)
Test passes? You're testing existing behavior. Fix test.
Test errors? Fix error, re-run until it fails correctly.
GREEN - Minimal Code
Write simplest code to pass the test.
Don't add features, refactor other code, or "improve" beyond the test.
Verify GREEN - Watch It Pass
MANDATORY.
npm test path/to/test.test.ts
Confirm:
- Test passes
- Other tests still pass
- Output pristine (no errors, warnings)
Test fails? Fix code, not test.
Other tests fail? Fix now.
REFACTOR - Clean Up
After green only:
- Remove duplication
- Improve names
- Extract helpers
Keep tests green. Don't add behavior.
Repeat
Next failing test for next feature.
Good Tests
| Quality | Good | Bad |
|---|---|---|
| Minimal | One thing. "and" in name? Split it. | test('validates email and domain and whitespace') |
| Clear | Name describes behavior | test('test1') |
| Shows intent | Demonstrates desired API | Obscures what code should do |
Why Order Matters
"I'll write tests after to verify it works"
Tests written after code pass immediately. Passing immediately proves nothing:
- Might test wrong thing
- Might test implementation, not behavior
- Might miss edge cases you forgot
- You never saw it catch the bug
Test-first forces you to see the test fail, proving it actually tests something.
"I already manually tested all the edge cases"
Manual testing is ad-hoc. You think you tested everything but:
- No record of what you tested
- Can't re-run when code changes
- Easy to forget cases under pressure
- "It worked when I tried it" ≠ comprehensive
Automated tests are systematic. They run the same way every time.
"Deleting X hours of work is wasteful"
Sunk cost fallacy. The time is already gone. Your choice now:
- Delete and rewrite with TDD (X more hours, high confidence)
- Keep it and add tests after (30 min, low confidence, likely bugs)
The "waste" is keeping code you can't trust. Working code without real tests is technical debt.
"TDD is dogmatic, being pragmatic means adapting"
TDD IS pragmatic:
- Finds bugs before commit (faster than debugging after)
- Prevents regressions (tests catch breaks immediately)
- Documents behavior (tests show how to use code)
- Enables refactoring (change freely, tests catch breaks)
"Pragmatic" shortcuts = debugging in production = slower.
"Tests after achieve the same goals - it's spirit not ritual"
No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"
Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones.
Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't).
30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work.
Common Rationalizations
| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for existing code. |
Red Flags - STOP and Start Over
- Code before test
- Test after implementation
- Test passes immediately
- Can't explain why test failed
- Tests added "later"
- Rationalizing "just this once"
- "I already manually tested it"
- "Tests after achieve the same purpose"
- "It's about spirit not ritual"
- "Keep as reference" or "adapt existing code"
- "Already spent X hours, deleting is wasteful"
- "TDD is dogmatic, I'm being pragmatic"
- "This is different because..."
All of these mean: Delete code. Start over with TDD.
Example: Bug Fix
Bug: Empty email accepted
RED
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
Verify RED
$ npm test
FAIL: expected 'Email required', got undefined
GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
Verify GREEN
$ npm test
PASS
REFACTOR Extract validation for multiple fields if needed.
Verification Checklist
Before marking work complete:
- Every new function/method has a test
- Watched each test fail before implementing
- Each test failed for expected reason (feature missing, not typo)
- Wrote minimal code to pass each test
- All tests pass
- Output pristine (no errors, warnings)
- Tests use real code (mocks only if unavoidable)
- Edge cases and errors covered
Can't check all boxes? You skipped TDD. Start over.
When Stuck
| Problem | Solution |
|---|---|
| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. |
| Test too complicated | Design too complicated. Simplify interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify design. |
Debugging Integration
Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.
Never fix bugs without a test.
Testing Anti-Patterns
When adding mocks or test utilities, read @testing-anti-patterns.md to avoid common pitfalls:
- Testing mock behavior instead of real behavior
- Adding test-only methods to production classes
- Mocking without understanding dependencies
Final Rule
Production code → test exists and failed first
Otherwise → not TDD
No exceptions without your human partner's permission.
先判断是否适合
作者设计意图
作者的方法与取舍
边界和复核