{/* 本页面由 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

未经用户明确许可,没有例外。