mcpField
T
test-first

test-first

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

关于 test-first

test-first 是面向 Agent 的 TDD 技能,要求先写出失败测试再编写生产代码。它指导团队在约定的公共测试接口上逐个完成测试与实现,并在结束前运行适用范围的任务检查。

主要功能

  1. 01生产代码前先写失败测试
  2. 02按垂直切片循环测试与实现
  3. 03测试限于约定的公共接口
  4. 04任务门禁与最终检查
  5. 05需求关联信息保留在文档中

适用场景

  1. 01通过测试优先循环添加行为
  2. 02先复现缺陷再进行修复
  3. 03完成前检查变更单元及其依赖项

安装

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

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

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

附带文件eval.jsonTESTS.md

详细介绍

TDD

铁律

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

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

只有获得用户明确许可时才存在例外:一次性运行验证、生成的代码、纯配置。“这次就跳过”不算例外;这正是这项技能要阻止的自我开脱。

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

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

循环

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

RED——编写一个失败的测试。 一种行为;使用能以领域语言描述该行为的名称;在约定的接缝处针对真实代码设置预期。不要要求在应用或测试源码中嵌入需求 ID(仅限文档的主线)。在任务报告/Spec 审查中,将测试对应到其需求 ID。修复 Bug?必需的子技能:先使用 root-cause 确定根本原因——RED 测试必须复现该 Bug,并在任何修复之前因该 Bug 的原因而失败。

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

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

验证 GREEN——必须执行。 运行能构建并运行新测试目标的最窄范围命令(单文件模式)。确认:新测试通过,该次运行中的所有其他测试仍然通过,且输出干净——零警告、零无关错误,没有任何“打算之后清理”的内容。新测试失败?修复代码,绝不要放宽测试。其他测试失败?立即修复,再做其他事情。

REFACTOR——仅在测试保持绿色时进行。 消除重复、改进命名、提取辅助函数。每次修改后都重新运行相同命令。不添加行为。

然后处理下一个切片。

任务门禁——在你宣告工作完成之前,运行一次

上面的每一步都保持精简;广度只在最终提交时付出一次:

  1. 对你修改的每个单元,以及所有依赖它的单元运行测试——使用项目的 Task gate 命令;否则从构建图中推导(已修改的包及其反向依赖)。
  2. 对相同单元运行Lint 和格式检查,并以会话开始时记录的 Lint 基线为准。基线中没有的问题就是你引入的。绝不要为了让红色基线通过而禁用 Lint 规则。
  3. 当更改涉及构建图无法识别的内容时,升级为运行整个测试套件:模式或迁移、共享夹具或测试数据、构建清单或功能标志、生成的契约、几乎所有内容都依赖的根包。由你判断并说明理由。

当运行整个测试套件只需几分钟时,它就是门禁——运行它。报告你运行的范围:Task gate: <commands> — covering <units>,或 Whole suite: <command> — escalated: <reason>。门禁运行绝不能冒充整个测试套件;整个测试套件应在收尾时运行。

需求关联(仅限文档)

需求 ID 位于 docs/specs/** 中(requirements.md、设计文档中的 Satisfies:、 任务 _Requirements: 页脚)。测试使用领域语言验证行为。

  • 对于消费者应用代码,不要要求 /// REQ:、@CODE-N.M 或测试标题中的 ID。audit-trace 会忽略旧注释。
  • 确保每个新行为都对应任务简报中的一项需求;若没有需求涵盖该行为,请指出缺口(级别 ≥1),而不是自行扩展范围。
  • 例外:此技能集仓库自身的 pack 测试可以嵌入可搜索的 CODE-N.M 标记,作为测试该 pack 的产品夹具——这不是面向消费者的约定。

如果缺少 docs/agents/project.md,请说明这一点并建议使用 configure-repo;仍应在商定的接口处编写以领域命名的测试。

测试标准文档(可选)

**适用范围:**接口、模拟边界或测试套件命令。加载: skills/project/define-system-doc/consult-recipe.md。路径:docs/standards/testing.md (pack SSOT 视为已包含的常设规则)。若文件存在,则在本技能的 Iron Law 之外一并应用(Iron Law 的要求不得减弱)。若文件不存在,则不采取行动;只有在确有必要时才建议一次 /define-system-doc standards/testing;绝不自动调用。

反模式及其应对方式

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

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

合理化借口

想法现实
“太简单了,不值得测试”简单的代码也会出错;测试只花一分钟
“我马上就补上测试”事后编写的测试一运行就通过,什么也证明不了——你从未看到它捕获任何问题
“事后测试在本质上也一样”事后测试回答“这段代码做什么?”;先写测试回答“这段代码应该做什么?”——前者会受你的实现影响
“我已经手动测试过了”临时进行、没有记录、无法重现。下次修改时就消失了
“删除数小时的工作太浪费了”沉没成本。保留未测试的代码才是真正的浪费
“我写测试时先把它留作参考”这就是事后测试,只不过多了几步
“测试很难写”难以测试就意味着难以使用——简化接口,不要跳过测试
“TDD 会拖慢我”比敲代码慢,比调试快
“这个文件反正没有测试”你正在修改它;你的改动就应该有测试
“为了保险,每次 GREEN 都运行完整测试套件”每项任务运行 6 次、每次 18 分钟,毫无益处;任务门禁和结束时的运行已足够。每一步都缩小范围,只在门禁时全面检查一次
“限定范围的运行通过了——测试都通过了”说清楚你运行了什么范围。“测试都通过了”是对整个测试套件的结论
“Lint 原本就报错,所以放行这条规则/没法检查”与基线比较。抑制这条规则会让旧有问题掩盖你的发现
“这只是迁移文件,crate 测试会覆盖它”每项测试都会读取该模式。升级处理级别

危险信号——停止并重新开始

  • 已有实现,但没有对应的测试
  • 新测试第一次运行就通过
  • 你说不清测试在 RED 阶段为何失败
  • 测试针对的接缝不在设计的接缝表中
  • 新行为无法对应需求说明中的要求(级别 ≥1)
  • “就这一次”“留作参考”“重精神不重形式”“这个情况不一样”

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

声称完成之前

  • 每项新行为都有测试,并且你先亲眼看到它因预期原因失败——错误路径和边界值也算行为
  • 最终提交的任务门禁通过,并注明其范围;或者运行完整测试套件并说明升级处理原因——输出干净,且没有超出基线的 Lint 问题
  • 每项新行为都对应任务说明/报告中的一项要求(ID 保留在文档中,不必写在测试源码里)
  • 所有测试都位于 design.md 中约定的接缝处(或与用户约定的接缝处)
  • 仅在系统边界使用 Mock,数据结构完整,且不对 Mock 作断言

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

评论

支持 Markdown