mcpField
T
tdd

tdd

github.com/jayden-dang测试与质量148 次安装5 天前更新
代码仓库

关于 tdd

tdd 是面向编码 Agent 的测试驱动开发技能,要求先编写并验证失败测试,再逐步实现和重构。它强调在约定的测试边界上验证行为,并为测试关联需求 ID。

主要功能

  1. 01先写失败测试再实现
  2. 02逐步执行红绿重构循环
  3. 03要求确认测试先按预期失败
  4. 04在约定的公共边界编写测试
  5. 05为测试标注需求 ID
  6. 06检查测试替身与完成条件

适用场景

  1. 01为新行为建立测试优先的实现流程
  2. 02修复缺陷前编写可复现问题的测试
  3. 03按需求追踪测试覆盖与边界
  4. 04完成改动前检查测试验证条件

安装

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

$npx skills add https://github.com/jayden-dang/skills --skill tdd

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

详细介绍

TDD

铁律

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

先写了实现再写测试?删掉它。不是“留着当参考”,也不是“等测试补上时再调整”——你最终会测试自己构建的东西,而不是需求所要求的内容。删除就是删除;从测试开始重新实现。违反这条铁律的字面要求,就是违背它的精神。

只有得到用户明确许可时才存在例外:一次性原型、生成的代码、纯配置。“这次就跳过吧”不算例外;这正是这项技能要阻止的自我辩解。

测试应放在哪里:预先约定的接缝

接缝是一个公共接口,无需深入内部即可观察行为。测试只能放在功能的 design.md 中 Seams for testing 所列出的接缝上。如果表格未确认某个接缝,就拒绝在该接缝上编写测试。此项工作没有 design.md(第 0/1 级变更、遗留区域)?先向用户提出接缝方案并取得同意,再编写第一个测试。测试投入是一项预算——约定好的接缝能让投入聚焦于关键路径,而不是每个内部细节。

循环

一次只处理一个垂直切片:一种行为 → 一个测试 → 一个最小实现 → 重复。每个循环都要根据上一个循环的发现进行调整。不要先一次性写完所有测试,再写完所有代码——批量编写的测试验证的是想象出来的行为。

RED——编写一个失败的测试。 一个行为,一个能说明该行为的名称,在约定的接缝上针对真实代码编写断言。标记该测试所验证的需求 ID(见下文)。修复 Bug?必需的子技能:先使用 debug 查明根本原因——RED 测试必须复现该 Bug,并且在修复前因该 Bug 而失败。

验证 RED——必须执行,绝不跳过。 运行测试(使用 docs/agents/project.md 中的单文件命令)。确认测试确实失败、失败消息符合预期,并且失败原因是缺少该行为——而不是拼写错误或导入错误。测试立即通过? 你测试的是已存在的行为——修改测试,而不是代码。测试报错而非失败?修复错误并重新运行,直到测试正确失败。

GREEN——编写能通过测试的最简单代码。 不添加推测性的参数、不增加额外功能、不触碰无关代码。

验证 GREEN——必须执行。 运行完整测试套件。确认:新测试通过、其他测试仍全部通过,并且输出干净无误——零警告、零无关错误,也没有任何“打算之后清理”的内容。新测试失败?修复代码,绝不要放宽测试。其他测试失败?立即修复,然后再做其他事。

REFACTOR——仅在测试全绿时进行。 消除重复、改进命名、提取辅助函数。每次修改后都重新运行测试套件。不添加任何行为。

然后处理下一个切片。

需求标记

每个测试都要标记其所验证的需求 ID,并遵循 docs/agents/project.md 中的约定(Test annotation conventions 表)常见形式如下:

层级约定
E2E(例如 Playwright){ tag: ['@CODE-N.M'] }
单元测试(例如 Vitest)annotate('CODE-N.M', 'requirement') 或将 ID 写入测试名称
编译型语言(例如 Rust)在测试上方添加 /// REQ: CODE-N.M 文档注释

没有需求覆盖该行为?对于第 ≥1 级的工作,这是一个缺口——请向用户指出,而不是交付无法追踪的测试。如果缺少 docs/agents/project.md,请说明这一点,建议运行 setup-repo,并退而将 ID 写入测试名称。

反模式及其应对

反模式表现应对
同义反复的测试预期值用与代码相同的方式重新计算(expect(sum(items)).toBe(items.reduce(...))),因此测试必然通过预期值来自独立的事实来源:已知正确的字面值、完整示例或需求文本
与实现耦合的测试重构后即使行为未变,测试也会失败;断言调用次数或顺序、私有方法,或通过旁路验证(例如直接查询原始数据库,而不是使用接口)仅通过公共接口断言可观察到的行为
测试模拟行为断言检查模拟对象是否存在或是否被调用;移除模拟对象后测试就会失败测试真实组件,或者完全停止对模拟对象进行断言
仅供测试使用的生产类方法某个方法(例如 destroy())只在测试文件中调用将其移至测试工具;生产类只保留生产 API
不完整的模拟对象模拟对象只包含当前测试恰好会读取的字段;集成时遗漏的字段会导致问题复刻完整的真实数据结构——包含真实响应中的每个字段
模拟自有模块为了“隔离”而替换内部协作者只在系统边界处模拟(外部 API、时间、随机性,有时也包括文件系统或数据库)。绝不要模拟自己编写的代码——如果觉得有必要这么做,说明设计耦合过紧:应改为注入依赖

如果模拟对象的设置代码比测试逻辑还长,或者移除模拟对象后测试就会失败,说明问题出在模拟对象上——优先通过真实组件编写集成测试。

自我辩解

想法事实
“太简单了,不值得测试”简单的代码也会出错;写测试只需一分钟
“我之后马上补测试”事后编写的测试一运行就通过,什么也证明不了——你从未见过它捕获任何问题
“事后测试本质上效果一样”事后测试回答“这段代码做什么?”,先写测试回答“这段代码应该做什么?”——前者会受到你的实现影响
“我已经手动验证过了”临时执行、没有记录、无法重复。下次改动时,这些验证就不复存在了
“删掉几个小时的工作太浪费了”沉没成本。保留未经测试的代码才是真正的浪费
“写测试时先留着,作为参考”这还是事后测试,只是多了几个步骤
“这个测试很难写”难以测试就意味着难以使用——简化接口,而不是跳过测试
“TDD 会拖慢我”比打字慢,比调试快
“这个文件本来就没有测试”你正在改它;你的改动就应该有测试

危险信号——停下来重新开始

  • 实现已经存在,但对应的测试不存在
  • 新测试第一次运行就通过了
  • 你说不清测试在 RED 阶段为何失败
  • 测试针对的接口不在设计的接口表中
  • 测试没有需求 ID
  • “就这一次”“先留着参考”“重在精神,不拘形式”“这次情况不同”

出现以上任一情况:删除未经测试的代码,回到 RED。

宣布完成前

  • 每个新行为都有一个你亲眼看着先失败、且因预期原因失败的测试——错误路径和边界值也算行为
  • 完整测试套件全部通过,输出干净(零警告、零错误)
  • 每项测试都按照 docs/agents/project.md 标注了对应的需求 ID
  • 所有测试都位于 design.md 中约定的接口处(或与用户约定的接口处)
  • 仅在系统边界处使用模拟对象,数据结构完整,不对模拟对象进行断言

有任何一项无法勾选?工作就还没完成。必需的子技能:在声称完成前使用 verify。

评论

支持 Markdown