{/* 本页面由 website/scripts/generate-skill-docs.py 从技能 SKILL.md 自动生成。请编辑源 SKILL.md 而非本页面。 */}
测试驱动开发
TDD:强制执行 RED-GREEN-REFACTOR,先测试后代码。
技能元数据
| 来源 | 内置(默认安装) |
| 路径 | skills/software-development/test-driven-development |
| 版本 | 1.1.0 |
| 作者 | Hermes Agent(改编自 obra/superpowers) |
| 许可证 | MIT |
| 平台 | linux, macos, windows |
| 标签 | testing, tdd, development, quality, red-green-refactor |
| 相关技能 | systematic-debugging, writing-plans, subagent-driven-development |
参考:完整 SKILL.md
:::info 以下是此技能被触发时 Hermes 加载的完整技能定义。这是技能激活时代理所看到的指令。 :::
测试驱动开发(TDD)
概述
先写测试。看着它失败。编写最简代码使其通过。
核心原则: 如果你没有看着测试失败,你就不知道它是否在测试正确的东西。
违反规则的文字就是违反规则的精神。
使用时机
始终:
- 新功能
- Bug 修复
- 重构
- 行为更改
例外(先问用户):
- 一次性原型
- 生成代码
- 配置文件
想着”就这一次跳过 TDD”?停下。那是合理化。
铁律
没有先有失败测试就不得有生产代码
在测试之前写了代码?删除它。重新开始。
没有例外:
- 不要保留作为”参考”
- 不要在写测试时”适配”它
- 不要看它
- 删除就是删除
从测试开始重新实现。句号。
红-绿-重构循环
红色 — 编写失败测试
编写一个最简测试,显示应该发生什么。
好测试:
def test_retries_failed_operations_3_times():
attempts = 0
def operation():
nonlocal attempts
attempts += 1
if attempts < 3:
raise Exception('fail')
return 'success'
result = retry_operation(operation)
assert result == 'success'
assert attempts == 3清晰名称,测试真实行为,一件事。
坏测试:
def test_retry_works():
mock = MagicMock()
mock.side_effect = [Exception(), Exception(), 'success']
result = retry_operation(mock)
assert result == 'success' # 重试次数呢?时序呢?模糊名称,测试 mock 而非真实代码。
要求:
- 每个测试一个行为
- 清晰描述性名称(名称中有”and”?拆开)
- 真实代码,非 mock(除非确实不可避免)
- 名称描述行为而非实现
验证红色 — 看着它失败
强制。永不跳过。
pytest tests/test_feature.py::test_specific_behavior -v确认:
- 测试失败(不是由于拼写错误导致的错误)
- 失败消息符合预期
- 失败因为功能缺失
测试立刻通过? 你在测试已有行为。修复测试。
测试报错? 修复错误,重新运行直到它正确失败。
绿色 — 最简代码
编写让测试通过的最简代码。不要多写。
好:
def add(a, b):
return a + b # 没有多余内容坏:
def add(a, b):
result = a + b
logging.info(f"Adding {a} + {b} = {result}") # 多余的!
return result不要添加功能、重构其他代码或”改进”超出测试范围。
在绿色阶段作弊是 OK 的:
- 硬编码返回值
- 复制粘贴
- 重复代码
- 跳过边界情况
我们在重构阶段修复。
验证绿色 — 看着它通过
强制。
pytest tests/test_feature.py::test_specific_behavior -v
pytest tests/ -q确认:
- 测试通过
- 其他测试仍然通过
- 输出整洁(无错误、警告)
测试失败? 修复代码,不是测试。
其他测试失败? 立即修复回归。
重构 — 清理
仅在绿色之后:
- 消除重复
- 改进命名
- 提取辅助函数
- 简化表达式
保持测试全程绿色。不添加行为。
如果重构期间测试失败: 立即撤销。采取更小的步骤。
重复
下一个行为的下一个失败测试。一次一个循环。
为什么顺序重要
“我之后会写测试来验证它工作”
之后写的测试立即通过。立即通过证明不了什么:
- 可能测试了错误的东西
- 可能测试了实现而非行为
- 可能遗漏了你忘记的边界情况
- 你从未看到它捕获 bug
测试优先迫使你看到测试失败,证明它确实在测试某些东西。
常见合理化
| 借口 | 现实 |
|---|---|
| ”太简单不需要测试” | 简单的代码也会坏。测试只需 30 秒。 |
| “我之后测试” | 立即通过的测试证明不了什么。 |
| “已经手动测试过了” | 非正式 ≠ 系统性。没有记录,无法重新运行。 |
| “删除 X 小时工作是浪费” | 沉没成本谬误。保留未经验证的代码是技术债务。 |
红牌 — 停下并重新开始
如果你发现自己做这些事中的任何一件,删除代码并用 TDD 重新开始:
- 在测试前写代码
- 实现后写测试
- 测试在首次运行时立即通过
- 无法解释测试为何失败
- “稍后”添加测试
- 为”就这一次”找理由
所有这些意味着:删除代码。用 TDD 重新开始。
最终规则
生产代码 → 测试存在并先失败
否则 → 不是 TDD
未经用户明确许可,没有例外。