欢迎回来

登录 EAKE AI,继续您的智能之旅

忘记密码?
还没有账号?立即注册

测试驱动开发

测试驱动开发

TDD:强制执行 RED-GREEN-REFACTOR,先写测试再写代码

Test-Driven Development (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 而不是真实代码。

    要求:

  • 每个测试一个行为
  • 清晰的描述性名称(名称中有"和"?拆开它)
  • 真实代码,不是 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
    

    不要添加功能,不要重构其他代码,不要在测试之外"改进"。

    在绿色中作弊是允许的:

  • 硬编码返回值
  • 复制粘贴
  • 重复代码
  • 跳过边界情况
  • 我们会在重构中修复它。

    验证绿色 — 看着它通过

    强制。

    
    # 运行特定测试
    pytest tests/test_feature.py::test_specific_behavior -v
    
    # 然后运行所有测试检查回归
    pytest tests/ -q
    

    确认:

  • 测试通过
  • 其他测试仍然通过
  • 输出干净(无错误、无警告)
  • 测试失败? 修复代码,而不是测试。

    其他测试失败? 现在修复回归。

    重构 — 清理

    只有在绿色之后:

  • 删除重复
  • 改进名称
  • 提取辅助函数
  • 简化表达式
  • 在整个过程中保持测试绿色。不要添加行为。

    如果测试在重构期间失败: 立即撤销。采取更小的步骤。

    重复

    下一个失败测试用于下一个行为。一次一个循环。

    为什么顺序重要

    "我会在之后写测试来验证它是否工作"

    代码之后写的测试立即通过。立即通过不能证明什么:

  • 可能测试了错误的东西
  • 可能测试实现,而不是行为
  • 可能错过你忘记的边界情况
  • 你从来没有见过它捕获 bug
  • 测试优先迫使你看到测试失败,证明它实际上测试了一些东西。

    "我已经手动测试了所有边界情况"

    手动测试是临时的。你认为你测试了所有东西但:

  • 没有记录你测试了什么
  • 代码改变时无法重新运行
  • 在压力下容易忘记情况
  • "我尝试时它工作了" 不等于 全面
  • 自动化测试是系统性的。它们每次以相同的方式运行。

    "删除 X 小时的工作是浪费的"

    沉没成本谬误。时间已经过去了。你现在的选择:

  • 删除并用 TDD 重写(高置信度)
  • 保留它并在之后添加测试(低置信度,可能有 bug)
  • "浪费"是保留你无法信任的代码。

    "TDD 是教条的,适应它意味着实用"

    TDD 是实用的:

  • 在提交前找到 bug(比之后调试更快)
  • 防止回归(测试立即捕获 break)
  • 记录行为(测试显示如何使用代码)
  • 启用重构(自由更改,测试捕获 break)
  • "实用"快捷方式 = 在生产中调试 = 更慢。

    "之后测试实现相同目标 — 这是精神而不是仪式"

    不是的。之后的测试回答"这做什么?"测试优先回答"这应该做什么?"

    之后的测试受你的实现偏见。你测试你构建的,而不是需要的。测试优先强制在实现之前发现边界情况。

    常见合理化

    借口现实
    "太简单不需要测试"简单代码会坏。测试需要 30 秒。
    "我之后测试"测试立即通过不能证明什么。
    "之后测试实现相同目标"之后测试 = "这做什么?"测试优先 = "这应该做什么?"
    "已经手动测试了"临时 不等于 系统化。没有记录,无法重新运行。
    "删除 X 小时是浪费的"沉没成本谬误。保留未经验证的代码是技术债务。
    "保留作为参考,先写测试"你会改编它。那是之后测试。删除意味着删除。
    "需要先探索"可以。抛弃探索,用 TDD 开始。
    "测试难 = 设计不清晰"听测试的。难测试 = 难使用。
    "TDD 会让我变慢"TDD 比调试更快。实用 = 测试优先。
    "手动测试更快"手动不能证明边界情况。每次更改你都会重新测试。
    "现有代码没有测试"你在改进它。为你接触的代码添加测试。

    红旗 — 停止并重新开始

    如果你发现自己做任何这些,删除代码并用 TDD 重新开始:

  • 代码在测试之前
  • 测试在实现之后
  • 测试在第一次运行时立即通过
  • 无法解释为什么测试失败
  • 之后添加的测试
  • 合理化"就这一次"
  • "我已经手动测试了"
  • "之后测试实现相同目的"
  • "保留作为参考"或"改编现有代码"
  • "已经花了 X 小时,删除是浪费的"
  • "TDD 是教条的,我实用"
  • "这是不同的因为..."
  • 所有这些意味着:删除代码。用 TDD 重新开始。

    验证清单

    在标记工作完成之前:

  • [ ] 每个新函数/方法都有测试
  • [ ] 在实现之前看着每个测试失败
  • [ ] 每个测试因为预期原因失败(功能缺失,不是打字错误)
  • [ ] 写了最少的代码让每个测试通过
  • [ ] 所有测试通过
  • [ ] 输出干净(无错误、无警告)
  • [ ] 测试使用真实代码(mock 仅在不可避免时)
  • [ ] 覆盖边界情况和错误
  • 无法勾选所有框?你跳过了 TDD。重新开始。

    遇到困难时

    问题解决方案
    不知道如何测试写出你希望的 API。先写断言。问用户。
    测试太复杂设计太复杂。简化接口。
    必须 mock 一切代码太耦合。使用依赖注入。
    测试设置巨大提取辅助函数。还是复杂?简化设计。

    Hermes Agent 集成

    运行测试

    在每一步使用终端工具运行测试:

    
    # 红色 — 验证失败
    terminal("pytest tests/test_feature.py::test_name -v")
    
    # 绿色 — 验证通过
    terminal("pytest tests/test_feature.py::test_name -v")
    
    # 完整套件 — 验证无回归
    terminal("pytest tests/ -q")
    

    与 delegate_task 一起使用

    当分派子代理进行实现时,在目标中强制 TDD:

    
    delegate_task(
        goal="使用严格 TDD 实现 [功能]",
        context="""
        遵循 test-driven-development skill:
        1. 先写失败的测试
        2. 运行测试验证它失败
        3. 写最少的代码让测试通过
        4. 运行测试验证它通过
        5. 如需要重构
        6. 提交
    
        项目测试命令:pytest tests/ -q
        项目结构:[描述相关文件]
        """,
        toolsets=["terminal", "file"]
    )
    

    与 systematic-debugging 一起使用

    找到 bug?写重现它的失败测试。遵循 TDD 循环。测试证明修复并防止回归。

    永远不要在没有测试的情况下修复 bug。

    测试反模式

  • 测试 mock 行为而不是真实行为 — mock 应该验证交互,而不是替换被测系统
  • 测试实现细节 — 测试行为/结果,而不是内部方法调用
  • 仅快乐路径 — 总是测试边界情况、错误和边界
  • 脆弱的测试 — 测试应该验证行为,而不是结构;重构不应该破坏它们
  • 最终规则

    
    生产代码 → 测试存在且先失败
    否则 → 不是 TDD
    

    没有用户的明确许可不能有例外。

    安装指南

    复制下方命令,在终端运行即可安装:

    # 安装到当前项目
    npx skills add test-driven-development
    # 全局安装 — 所有项目可用
    npx skills add test-driven-development -g

    使用指南

    安装完成后,在对话框中直接使用此技能。

    基本信息
    作者 Community 分类 coding 难度 Intermediate 时长 1 hour
    🛠️ 安装命令
    # 安装到当前项目
    npx skills add test-driven-development
    # 全局安装
    npx skills add test-driven-development -g

    发表评论