mcpField
T
tdd

tdd

github.com/gusen1453测试与质量336 次安装6 周前更新
代码仓库

关于 tdd

tdd 是一项将测试驱动开发用于新功能、修复、重构和行为变更的技能。它要求先确认测试因预期原因失败,再编写最少实现使其通过,并在不增加新行为的前提下重构。

主要功能

  1. 01先于生产代码编写行为测试
  2. 02确认测试因预期原因失败
  3. 03编写通过测试所需的最少代码
  4. 04实现后运行相关测试
  5. 05仅在测试保持通过时重构

适用场景

  1. 01通过测试优先的循环开发新功能
  2. 02为错误修复添加回归测试
  3. 03在保留测试覆盖的同时重构行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/gusen1453/creed --skill tdd

安装后在 Claude Code 中输入/tdd确认已加载。

详细介绍

TDD

输出语言: 使用用户自己的语言回答并编写产物(匹配用户的消息,而不是仓库的历史)。

概述

先编写测试。观察它失败。编写通过测试所需的最少代码。重构。

核心原则:如果你没有观察到测试失败,就不知道它是否测试了正确的内容。

必须搭配使用: 使用 test-design 决定要提供什么输入、断言什么以及模拟什么。此技能只规定节奏。形式主义的 RED 测试仍然不允许——失败的戏码测试证明不了任何事。

适用场景

始终适用: 新功能、错误修复、重构、行为变更。

例外情况(先询问用户): 一次性原型、生成的代码、没有行为的纯配置。

心里想「这次就跳过 TDD 吧」?停下——这只是找借口。

铁律(不可妥协)

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

先写了代码再写测试?删掉代码。从头开始。

没有例外:

  • 不要把它留作「参考」
  • 不要在编写测试时对它进行「改造」
  • 不要查看它
  • 删除就是彻底删除

违反字面要求就是违背其精神。

工作流(调用时)

宣布「正在使用 tdd 来……」,然后每个循环步骤创建一个待办事项:

  1. RED — 编写一个失败的行为测试(应用 test-design:契约、精简断言、手写 expected)
  2. 验证 RED — 运行测试;确认它因正确原因失败(缺少功能,而不是拼写错误/错误)
  3. GREEN — 编写通过测试所需的最少生产代码
  4. 验证 GREEN — 运行测试;所有相关测试均通过;输出无误
  5. 重构 — 清理代码;保持通过;不添加新行为
  6. 重复 — 下一个行为 → 下一个 RED

之后发现错误?先编写能复现错误的失败测试,然后执行相同的循环。

Red → Green → Refactor

RED — 编写一个失败的测试

一个行为。名称清晰(角色 + 场景 + 预期)。使用真实协作者,除非 IO 迫使你使用 Fake/Recording(参见 test-design)。

test("retries failed operations 3 times", async () => {
  let attempts = 0
  const operation = () => {
    attempts++
    if (attempts < 3) throw new Error("fail")
    return "success"
  }
  expect(await retryOperation(operation)).toBe("success")
  expect(attempts).toBe(3)
})

不好的做法:名称含糊、只断言模拟调用次数,或使用生产逻辑计算 expected。

验证 RED — 必须执行

运行目标测试。确认:

  • 测试失败(而不是因拼写错误/导入问题报错)
  • 失败信息与缺少的行为相符
  • 如果测试立即通过 → 你测试的是现有行为,或没有做任何有意义的断言——修正测试
  • 如果测试报错 → 修复测试框架,然后重新运行,直到测试正确地失败
GREEN — 最少代码

编写能通过测试的最简单代码。不添加额外功能,不顺手重构,不添加 YAGNI 式选项对象。

验证 GREEN — 必须执行

再次运行。所有相关测试均通过。不忽略新的警告/错误。

重构

只在测试通过后进行:调整名称、消除重复、提取辅助函数。保持测试通过。不添加行为(新行为 → 新 RED)。

好测试与坏测试(从节奏角度看)

好坏
规模一个行为;名称中有「和」→ 拆分包罗万象的测试
名称描述可观察到的行为test1 / works
证明你观察到测试因正确原因进入 RED首次运行就通过
深度断言契约(test-design)只有 toHaveBeenCalled / 用覆盖率充数

合理化借口对照表

借口事实
“我之后会测试验证”测试立即通过什么也证明不了。你根本没看到它捕获这个 Bug。
“之后测试也能达到相同目标”之后 =“这段代码做什么?”首先 =“这段代码应该做什么?”
“已经手动测试过了”临时测试,没有记录,也无法重跑。
“太简单,不值得测试”简单的代码也会出错。编写一个有针对性的测试成本很低。
“删除 X 小时的工作很浪费”沉没成本。未经验证的代码就是债务。删除并重新开始。
“先留着作参考/测试时改编”那就是先实现再测试。删除就是真的删除。
“得先探索一番”先探索,然后丢弃探索代码;从头开始,干净地执行 TDD。
“TDD 会拖慢我”在生产环境中调试更慢。务实的做法 = 测试优先。
“很难测试”设计异味。简化接口(test-design § hard-to-test)。
“现有代码没有测试”你正在修改它——为你涉及的行为添加失败测试。
“就这一次/重精神不重仪式”字面要求就是精神要求。未经用户同意,不得例外。

危险信号——删除代码并重新开始

  • 在失败测试之前编写生产代码
  • 实现之后才编写测试
  • 测试首次运行就通过
  • 无法解释 RED 为什么失败
  • 使用 --no-verify/“为了节省时间”跳过测试运行
  • 将测试前的代码留作“参考”
  • “务实的捷径”/“这次情况不同,因为……”

任意一项出现 → 删除生产代码。从 RED 重新开始。

反模式(简要)

  • 测试模拟对象的行为,而不是单元本身
  • 在生产类型上添加仅供测试使用的方法
  • 模拟一堆相邻组件(通常说明代码只是胶水 → 优先选择集成测试,或重新设计——test-design §1/§5)

检查清单(每种行为)

  • 值得编写真正的测试吗?(test-design ROI)——否则别在胶水代码上假装使用 TDD
  • 根据契约编写 RED;观察到它因正确原因失败
  • GREEN 保持最精简;验证通过
  • REFACTOR 阶段保持绿色;没有偷偷加入新行为
  • 只为外部环境使用模拟;断言范围精确且难以满足

交接

转换关卡——当一个功能切片完成时(或离开 tdd 之前),询问用户(使用一次 AskUserQuestion;绝不自动继续):

A) Recommended: Proceed → review (then commit-and-push)
B) Review first — run **review** on the code slice (`references/code.md`; upstream = plan tasks / spec), then return to this gate
C) Adjust — back into a tdd RED→GREEN cycle for the issue, then return to this gate
D) Something else (I will type it)
  • 测试用例质量不明确 → test-design
  • 边界/模拟堆积 → solid
  • 功能形态不明确 → grill → 先执行 write-spec,再进行 TDD
  • 准备发布 → review,然后 commit-and-push

评论

支持 Markdown