Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when implementing any feature or bugfix in the project, before writing implementation code
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 292% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 116% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 164% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 104% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 114% | 0% |
没有先失败的测试,就不写生产代码
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST已经先写了实现代码?删掉它,从头开始。
没有任何例外:
> 违反规则的字面意思,就是违反规则的精神。
以下情况必须使用 TDD:
以下情况需询问用户是否跳过:
如果你在想"这次先跳过 TDD"——立刻停止。这是自我合理化。
mermaidflowchart TD Red[RED<br/>写一个失败的测试] --> VerifyRed{确认失败原因正确?} VerifyRed -->|测试抛异常/报错| FixRed[修复测试本身的问题<br/>拼写错误、导入缺失等] FixRed --> VerifyRed VerifyRed -->|因功能缺失而失败| Green[GREEN<br/>写最简实现] Green --> VerifyGreen{全部测试通过?} VerifyGreen -->|有测试失败| Green VerifyGreen -->|全部通过| Refactor[REFACTOR<br/>清理代码] Refactor -->|保持通过| VerifyGreen VerifyGreen -->|重构完成| Next([下一个测试]) Next --> Red
写一个最小的测试,明确展示你期望发生什么。
好测试示例:
pythondef test_retries_failed_operations_3_times(): """失败的操作应该被重试 3 次""" attempts = 0 def operation(): nonlocal attempts attempts += 1 if attempts < 3: raise Error('fail') return 'success' result = retry_operation(operation) assert result == 'success' assert attempts == 3
✅ 名称清晰、测试真实行为、只做一件事
坏测试示例:
pythondef test_retry_works(): mock = Mock() mock.side_effect = [Error(), Error(), 'success'] retry_operation(mock) assert mock.call_count == 3
❌ 名称模糊、测试的是 Mock 而非真实代码
写好测试的要求:
强制步骤。严禁跳过。
bash# Python python -m pytest path/to/test.py -v # JavaScript npm test path/to/test.test.js
确认以下几点:
测试直接通过了? 说明你在测已有行为。修改测试,让它测新行为。
测试报错抛异常? 修复测试本身的错误,重新运行,直到它因正确的理由失败。
写最简单的代码让测试通过。
好的最简实现:
pythondef retry_operation(fn): for i in range(3): try: return fn() except Exception as e: if i == 2: raise e raise Error('unreachable')
刚好足够通过测试,不多不少。
过度设计的反面教材:
pythondef retry_operation( fn, max_retries=3, backoff='linear', on_retry=None ): # YAGNI —— 你目前不需要这些 pass
不要添加功能、不要重构其他代码、不要"改进"到超出当前测试的范围。
强制步骤。
bashpython -m pytest path/to/test.py -v
确认:
新测试失败? 修复实现代码,不是修改测试。
其他已有测试失败? 立即修复,不能留到后面。
只在测试变绿之后:
保持所有测试通过。不要添加新行为。
为下一个功能写下一个失败测试,循环往复。
| 特征 | 好的做法 | 坏的做法 | |------|---------|---------| | 最小化 | 只测一件事。如果测试名里有"和",拆开。 | test_validates_email_and_domain_and_whitespace() | | 清晰 | 名称描述行为 | test1() | | 展示意图 | 展示期望的 API 用法 | 隐藏代码应该做什么 |
之后写的测试一跑就通过。立即通过的测试证明不了任何东西:
先写测试迫使你看到它失败,证明它确实在测某些东西。
手动测试是不可靠的。你以为你都测了,但:
自动化测试是系统性的。每次运行都一样。
这是沉没成本谬误。时间已经花掉了。现在的选择:
真正的"浪费"是你无法信任的代码。没有真实测试覆盖的代码是技术债务。
TDD 本身就是实用的:
所谓"实用"的捷径 = 生产环境调试 = 实际上更慢。
| 借口 | 真相 | |------|------| | "太简单了,不需要测试" | 简单代码也会坏。写个测试只需 30 秒。 | | "我会之后补测试" | 之后写的测试立即通过,证明不了什么。 | | "之后测和先测效果一样" | 之后 = "这代码做了什么?" 先 = "这代码应该做什么?" | | "我已经手动测过了" | 临时测试 ≠ 系统性测试。无记录,无法重复运行。 | | "删掉 X 小时是浪费" | 沉没成本谬误。保留未验证的代码才是技术债务。 | | "保留作为参考,我先写测试" | 你会忍不住适配它。这就是"之后测试"。删意味着删。 | | "我需要先探索一下" | 可以。但探索完要丢弃,用 TDD 重新开始。 | | "这代码太难测了" | 听测试的。难测 = 难用 = 设计有问题。 | | "TDD 会拖慢我" | TDD 比调试快。实用 = 先测试。 | | "手动测试更快" | 手动测证明不了边界情况。每次修改都要重新手动测。 | | "现有代码本来就没测试" | 你正在改进它。给现有代码补测试。 |
| 想法 | 现实 | |------|------| | "先写点代码再补测试" | 立刻停止,删掉代码,用 TDD 重新开始 | | "实现完了再补测试" | 之后立即通过的测试证明不了什么 | | "测试怎么直接通过了" | 你在测已有行为,不是新行为 | | "我说不清为什么这个测试会失败" | 测试必须有明确意图。说不清 = 测试有问题 | | "稍后我再补测试" | 稍后 = 永远不会。先测试。 | | "就这一次跳过" | "就这一次"合理化 = 每次都会跳过 | | "我已经手动测过了" | 临时 ≠ 系统性。无记录,不能重复运行。 | | "之后测效果一样" | 之后 = "这做了什么?" 先 = "这应该做什么?" | | "这是精神不是仪式" | 违反规则的字面意思就是违反精神 | | "保留作为参考"或"适配现有代码" | 你会适配它。删意味着删。 | | "已经花了 X 小时,删了浪费" | 沉没成本谬误。保留未验证代码是技术债务。 | | "TDD 教条,我实用" | TDD 比调试快。实用 = 先测试。 | | "这次情况特殊因为…" | 没有例外。删掉代码。用 TDD 重新开始。 |
以上所有情况都意味着:删掉代码。用 TDD 重新开始。
Bug 描述:空邮箱被错误地接受
pythondef test_rejects_empty_email(): result = submit_form({'email': ''}) assert result.error == '邮箱不能为空'
bash$ python -m pytest FAIL: expected '邮箱不能为空', got undefined
pythondef submit_form(data): if not data.get('email', '').strip(): return {'error': '邮箱不能为空'} # ...
bash$ python -m pytest PASS
为多字段提取通用验证逻辑(如需要)。
标记工作完成前,确认:
不能全部勾选?说明你跳过了 TDD。从头开始。
什么时候停止写新测试? 确认以下所有项:
测试覆盖:
代码质量:
验证状态:
判断:
> 红旗:"我觉得差不多了" = 你没有对照 Spec 逐条核对。用检查清单,不要凭感觉。
| 问题 | 解决方案 | |------|---------| | 不知道怎么写测试 | 先写期望的 API。先写断言。询问用户。 | | 测试写起来太复杂 | 设计太复杂了。简化接口。 | | 必须 Mock 所有东西 | 代码耦合太紧。使用依赖注入。 | | 测试的 setup 代码太庞大 | 提取辅助函数。还复杂?简化设计。 |
发现 Bug?先写一个能重现它的失败测试。然后遵循 TDD 循环。测试既证明修复有效,又防止未来回归。
绝不修复没有测试覆盖的 Bug。
生产代码 → 必须有先失败的测试
否则 → 这不是 TDD没有用户明确许可的例外。
前置 Skill: sw-working-plan(提供实现计划)
后续 Skill: sw-subagent-development
此 Skill 被调用时:
子 Agent 必须遵循:
Other measured skills in the registry, with their headline benchmark lift.