mcpField
T
tdd

tdd

github.com/pandoscope测试与质量11.9k 次安装5 周前更新
代码仓库

关于 tdd

面向编码 Agent 的测试驱动开发技能,规定先写并提交失败测试,再实现、重构。通过提交标记和钩子检查流程结构,同时强调这些机制不能证明测试确实按预期失败。

主要功能

  1. 01先写失败测试并提交
  2. 02按单个行为逐步实现
  3. 03区分 RED、GREEN 与重构提交
  4. 04使用标记和脚本检查提交结构
  5. 05通过公开接口验证行为

适用场景

  1. 01为新功能按 TDD 流程开发
  2. 02为缺陷修复编写复现测试
  3. 03检查提交是否符合 RED/GREEN 约定

安装

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

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

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

deep-modules.md
interface-design.md
lint-red.sh
mocking.md
refactoring.md
testing-anti-patterns.md
tests.md

详细介绍

测试驱动开发

先编写测试。提交红色测试。编写通过测试所需的最少代码。提交绿色代码。重构。

**核心原则:**测试通过公共接口验证行为,而非实现。代码可以彻底改变;测试不应改变。如果重命名内部函数(行为不变)就会导致测试失败,那么这个测试测的是实现。删除它。

没有观察到失败,就无法证明测试测对了。提交历史就是证据;钩子会检查其结构。

示例见 tests.md,模拟指南见 mocking.md。

铁律

没有先提交失败的测试,就不能编写生产代码。

先写了代码再写测试?删除代码。从测试出发重新实现。删除就是彻底删除。例外情况(一次性原型、生成的代码、配置)需要人工批准。心里想着“就这一次跳过 TDD”?这就是自我开脱。

1. 规划(编写任何代码之前)

使用项目的领域术语表,确保测试名称和接口用语与代码库一致;遵守你要修改部分的 ADR。

  • 列出要测试的行为,而不是实现步骤。优先考虑关键路径和复杂逻辑——你无法测试所有内容。
  • 设计便于测试的接口;找出构建深模块(接口小、实现深入)的机会。
  • 与用户确认公共接口和优先测试的行为,然后开始。

2. 纵向切片,而非横向切片

不要先编写所有测试,再编写所有实现。这是横向切片,会产出糟糕的测试:批量编写的测试针对的是想象出来的行为和事物的形状(签名、数据结构),对真实变更不敏感,还会让你在理解代码之前就受测试结构所限。

采用纵向切片——一个测试 → 一个实现 → 重复。每个测试都应回应上一个周期带来的认知。

WRONG (horizontal):  RED: t1 t2 t3 t4   GREEN: i1 i2 i3 i4
RIGHT (vertical):    t1→i1  t2→i2  t3→i3 ...

第一个切片是示踪弹:证明整条路径端到端可用。

3. 提交协议

一个行为 = 一个 RED 提交 + 一个 GREEN 提交。每个分支可以有多个周期;在 Go/Rust 仓库中,最好一次只进行一个周期(见标记)。

**每次提交——包括 RED 提交——都必须确保整个代码树通过 lint。**每次提交后,都要对整个代码树运行仓库的完整钩子套件(例如 prek run --all-files):退出代码为 0,且没有文件被修改。如果自动修复器修改了文件,即视为失败——应用修复,并将其修订进引入问题的提交,然后再开始下一个周期。CI 只评估 PR 的最新提交,不能成为中间提交不干净的借口;每次提交的规范都必须在提交时验证,否则就无从验证。

STUB 提交(仅当 RED 测试需要尚不存在的 API 接口时)

RED 提交中只有测试是红色的:lint、格式和类型检查仍须通过。当新测试调用尚不存在的函数、方法或参数时,类型检查会失败——因此,先单独提交一个只有最简签名的存根,前缀为 chore(stub):。签名 + 文档字符串,不包含任何行为:新的可调用项抛出 NotImplementedError;新参数接受但忽略。这是铁律唯一认可的例外:存根定义的是接口,不得包含实现——存根提交中的任何逻辑,都是没有失败测试的生产代码。如果接口已存在,则完全跳过这一步。

RED 提交
  • 编写一个失败的测试。只测一种行为,名称清晰,使用真实代码——除非别无选择,否则不要使用 mock。
  • 不加标记运行;观察它因正确原因失败(缺少功能——行为错误、断言失败,或桩代码的 NotImplementedError——绝不能是拼写错误、导入错误或缺少符号)。如果立即通过?说明它测试的是现有行为——修正测试。
  • 添加标记(见下文),确保测试套件通过,然后提交。仅包含测试,前缀为 test(red):。
语言标记严格?
Python (pytest)@pytest.mark.xfail(strict=True)是——XPASS 会导致测试套件失败
TS/JS (vitest)test.fails(...) / it.fails(...)是
TS/JS (jest)it.failing(...)是
Go//go:build red + TestRed 前缀 + red-tests 作业仅聚合检查
Rust#[ignore = "red"] + red_ 前缀 + red-tests 作业仅聚合检查
其他找到严格的预期失败机制;若不存在 → 不加标记提交,并告知人类仓库缺少 red 强制检查不适用

Go/Rust 的构建标签/忽略标记会将测试排除在常规测试套件之外,因此 red-tests CI 作业必须只运行带标记的测试,并预期测试失败(没有此类测试时则不执行)。其退出代码是聚合结果:同时有两个 red 测试在进行,其中一个错误地通过了 → 作业仍会通过。严格标记会逐个测试捕获这种情况;作业本身不会。

GREEN 提交
  • 编写能通过测试的最简单代码。遵循 YAGNI——不要推测性地追求通用性。
  • 移除本轮的 red 标记。本次提交不做其他测试改动。
  • 确保完整测试套件通过,输出无异常。如果失败?修复代码,而不是测试。前缀为 feat:/fix:。
REFACTOR(仅在 green 后)

消除重复、改进名称、提取辅助函数、深化模块。测试保持通过,不添加新行为,单独提交。**绝不要在 red 阶段重构。**然后开始下一轮。

所有测试通过后,查找重构候选项:

强制检查及其局限

lint-red.sh(随 skill 提供;接入 prek + CI):staged/commit 模式会检查 test(red): 提交是否只修改测试文件并添加标记,以及其他提交是否未添加标记;merge 模式会拒绝树中存在的任何标记;如果存在 Go/Rust 标记,则必须有 red-tests 作业。

钩子会验证提交结构和标记规范,但不会验证你是否运行了未加标记的测试,并观察它因正确原因失败——这仍由你负责。每次提交的 lint 也一样:已安装的 pre-commit 钩子会在提交时阻止不规范的提交,但在未安装钩子的环境(全新的远程环境)中,每次提交后运行 --all-files 需要手动坚持——无论如何都要执行。严格标记可部分弥补这一点(XPASS 会捕获对现有行为的测试)。“Lint 通过”≠“TDD 已验证”。

好的测试

质量规则
行为性通过公共 API 覆盖真实路径;重构后仍然有效
精简一次只测一件事。名称里有“和”?拆开。
清晰名称描述行为,而不是 test1
诚实测试代码,而不是 mock
// Good — tests real behavior (vitest; jest: it.failing)
test.fails('retries failed operations 3 times', async () => {
  let attempts = 0;
  const op = () => { attempts++; if (attempts < 3) throw new Error('fail'); return 'ok'; };
  expect(await retryOperation(op)).toBe('ok');
  expect(attempts).toBe(3);
});

危险信号——停止、删除、重来

实现尚不存在时,未加标记的测试就通过了 · 无法解释测试为何失败 · 为了让 GREEN 通过而弱化断言 · 测试 mock · “就这一次” · “我是在讲求实效,TDD 太教条了。”

阅读 testing-anti-patterns.md,避免常见陷阱。

合理化借口一览

借口事实
“太简单,不值得测试”简单的代码也会出错。测试只需 30 秒。
“我之后再测试”事后测试会受实现影响,问的是“这段代码做了什么?”,而不是“它应该做什么?”
“删除几小时的代码太浪费”沉没成本。未经验证的代码就是债务。
“测试很难写”难以测试 = 难以使用。听从它的提示;简化接口。
“必须 mock 所有东西”耦合太紧。注入依赖。
“Lint 通过了,TDD 也完成了”钩子检查的是结构,而不是你是否观察了失败。

遇到困难 / 调试

不知道如何测试 → 先写出期望的 API 和断言。发现 Bug → 编写一个能复现它的失败测试,然后运行完整协议。修复 Bug 时务必先编写测试。

评论

支持 Markdown