关于 tdd
指导对可观察行为变更采用测试驱动开发的技能,使用 RED-GREEN-REFACTOR 循环。详述快速反馈、监视器安全和按代码库选择测试,并将完整测试留到 PR 就绪检查。
主要功能
- 01生产代码变更前编写行为测试
- 02聚焦的 RED-GREEN-REFACTOR 循环
- 03监视器选择与管理指南
- 04适用于单体仓库的受影响测试选择
- 05PR 就绪检查运行完整非监视测试
适用场景
- 01开发新的可观察行为
- 02先用失败测试驱动行为变更
- 03在单体仓库中选择测试反馈流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ citypaul/ .dotfiles --skill tdd安装后在 Claude Code 中输入/tdd确认已加载。
附带文件resources/vitest-watch-feedback.md
详细介绍
测试驱动开发
TDD 是新增或变更可观察行为的基本实践:每一项此类生产代码变更都必须针对一个失败的行为测试编写。
纯粹保持行为不变的工作则有所不同。refactoring 和 reduce-system-complexity 从通过且与变更规模相称的保持行为证据开始,并在内部结构变更期间保持行为测试通过。在阶段结束的 PR 就绪门禁中,在有意义时使用变异测试;否则记录可达性、配置、契约、集成或运维证据,并将变异测试标记为 N/A。不要为了让 REFACTOR 切片看起来处于 RED 状态而人为制造失败测试或结构变异体。如果工作改变了行为或修复了有争议的 bug,则回到 RED。
在本技能中,阶段指一个适合单个 PR、可独立合并的切片:即一个分支和 PR 的累积范围。它不指跨多个 PR 的功能,也不指 README.md 中编号的生命周期阶段。
关于如何编写良好的测试,请加载 testing 技能。本技能侧重于 TDD 工作流/流程。若要进行考虑变异测试的测试规划,请加载 mutation-testing 技能,并将其 resources/mutator-rules.md 资源作为权威依据。
快速 TDD 反馈循环(AI 默认)
RED-GREEN-REFACTOR 内循环必须优先保证反馈快速且相关。不要每次编辑后都运行完整测试套件。
在 RED 之前,检查包脚本、测试配置和运行器帮助信息。仓库自有命令优先于通用示例:
- 遵循仓库规定的监视器启动时机——如果仓库要求在首次编辑前先验证监视器可用,就现在启动一次。否则,严格的 TDD 可以先写出 RED,然后立即启动监视器。仓库规定的时机优先于下方的通用示例。
- RED——运行新增或变更的测试文件/名称,并确认出现预期的失败。
- 有意选择或复用监视器——优先使用经过测试的仓库自有监视命令。它可以先运行一次完整的相关开发层级,以填充运行时图,然后仅重新运行受影响的测试。对于 Vitest,如果没有此类命令,只有在已安装版本和仓库配置具备所需的实时验证时,才使用
--changed <real-base> --watch。 - GREEN/REFACTOR——在专用且可复用的终端/会话中保留一个监视器。让运行器或仓库图选择受影响的测试;不要在后续 GREEN 阶段手动挑选更小的测试范围。
- AI/非交互式备用方案——如果无法可靠地保持监视器运行,则在每次编辑后运行等效的受影响测试单次命令。
- 在正确的边界修复——对于过时或新近进入图中的关系,重启或重新播种。对于非导入/动态依赖、配置、全局设置、公共契约或共享包的不确定性,应添加仓库自有触发器,或将范围扩大到所属套件/层级;单纯重启无法创建缺失的图边。
- PR 就绪——停止所有监视器,然后遵循仓库的变异测试/替代证据策略,并完成非监视模式的 PR 门禁。在单体仓库中,最终门禁包括所有已配置的项目以及必需的集成/E2E 套件,而不仅是开发子集。
在选择、新增或变更可复用的 Vitest 监视器之前,请阅读 resources/vitest-watch-feedback.md。其中提供了关于种子策略、原生发现、盲点以及所需实时进程验证的权威细节。
Vitest 种子语义
vitest --changed <real-base> --watch 的首次执行由差异选择。Vitest 在完成选择后会清除 VCS 选择器;之后每次保存时,它不会持续重新计算 VCS 影响。后续重跑会使用保留的 Vite 模块图。在 Vitest 4.1.10 的常规 Node/SSR 路径中,首次变更选择会静态分析所有已发现的测试规范,因此,即使干净启动时的首次执行报告没有受影响的测试,修改已导入的实现也可能重跑其测试。因此,后续重跑不限于首次执行过的测试。
不要将 Vitest 4.1.10 的这一实现细节泛化到其他版本、Browser Mode、动态依赖或非导入依赖,也不要泛化到任意仓库配置。对于常规开发、建立干净的重构基线,或在 RED 阶段之前启动监听器,优先使用经过行为验证的仓库自有命令,例如 pnpm test:watch。仓库也可以安全地运行一次完整的相关测试层级,以填充模块图。对于严格的 TDD,在 RED 阶段之后启动经过验证的差异选择型监听器,可以轻松确认预期测试确实执行了。不要用通用的原始 Vitest 示例替换仓库命令。
示例(根据仓库情况调整;符合所需行为的首个现有命令优先):
# Repository-owned graph-complete watch, when defined and proven
pnpm test:watch
# Vitest diff-selected initial execution: use only after repository-level proof
pnpm exec vitest --changed origin/main --watch
# Vitest fallbacks: mechanically derived affected sources or one-shots
pnpm exec vitest related src/example.ts --watch
pnpm exec vitest related --run src/example.ts
pnpm exec vitest --changed origin/main --run
# Exact selector: RED/debug only
pnpm exec vitest run tests/example.test.ts -t "rejects empty names"
# Jest: repository/runner changed watch, or affected one-shot
pnpm exec jest --watch
pnpm exec jest --watch --changedSince=origin/main
pnpm exec jest --findRelatedTests src/example.ts --watch=false
pnpm exec jest --onlyChanged --watch=false
pnpm exec jest --changedSince=origin/main --watch=false
pnpm exec jest --runTestsByPath tests/example.test.ts -t "rejects empty names"
# Playwright: runner-selected affected one-shot (heuristic; inspect the selected tests)
pnpm exec playwright test --only-changed
pnpm exec playwright test --only-changed=origin/main
只要 include glob 保持开放,Vitest 就能原生发现新建的匹配测试文件。在依赖后续实现重跑之前,先确认新测试确实执行并加载了其实现。除非实时验证证明原生发现能力不足,否则不要添加自定义文件系统监听器或调用重跑 API;两者都使用可能导致新测试执行两次。
vitest related <sources...> 仅在所有源文件路径都以机械方式派生时作为后备方案。它默认允许空结果通过,因此请确认预期测试已执行。不要将 --passWithNoTests 当作证据。不要使用 --standalone:它要么在没有文件筛选器时跳过首次运行,要么将工作流缩减为仅使用提供的筛选器。
Watcher 生命周期与自动化安全
- 在专用且可复用的终端/会话中启动一个 watcher,并在整个内部循环中重复使用。
- 最后一次编辑完成后,在 PR 检查或任务交接之前用
Ctrl+C停止它。不要遗留后台 watcher。 - 自动化启动 watcher 时,超时和信号清理必须终止整个 watcher 进程组,而不只是它的直接父进程,并删除临时目录。
- 切勿在 CI、Git hooks、有限时长的 lint/test/build/verification 脚本,或其他设计为会终止的包脚本中调用 watch 命令。检测常见形式,包括交互式 TTY 中裸用
vitest、vitest watch、vitest --watch、vitest --watch=true、vitest -w和vitest -w=true。有限时长的 Vitest 命令必须通过vitest run、--run或--watch=false强制终止。 - 将同时包含 watch 子命令或启用 watch 的标志与有限时长标记的命令视为 watch 模式:
vitest run --watch和vitest watch --watch=false都不是有限时长命令。 - 唯一例外是以 watcher 本身为测试对象的隔离 live-proof 测试框架。它必须使用临时 fixture,强制设置硬超时,在成功或失败时终止整个 watcher 进程组,删除 fixture,并且绝不能将 watch 调用暴露为普通的有限时长包命令/CI 命令。
- 观察相关重新运行后输出的内容。过时的等待标记、零个测试,或因“无测试时视为通过”选项而显示为绿色的进程,都不能作为证据。
即使没有 TTY,显式启用 watch 的命令仍会长期运行;在 CI 中它不会变成有限时长命令。若 stdin 是 TTY,且 CI 和 Vitest 的 Agent 检测均未禁用 watch,裸用 vitest 可能默认进入 watch 模式。切勿依赖环境自动检测来确保有限时长命令终止。对于其他运行器,优先使用经过测试、由仓库维护的 watcher,其次是原生的变更/受影响项选择,再次是基于完整且以机械方式推导的源码列表进行依赖感知选择,然后是由依赖图推导的受影响包/项目集合,最后才使用一次性的受影响项测试。精确选择器仅用于 RED/调试。
Monorepos
应从仓库根目录,通过项目定义的测试命令、根级 Vitest projects 配置,或拥有完整工作区依赖图的 monorepo 编排器,运行变更/受影响项选择。共享包的编辑可能影响多个使用方的测试,因此不要仅根据正在编辑的文件就 cd 到某个包,或添加 --project。只有在 Vitest 或仓库的包/任务图以机械方式推导出完整的受影响项目集合(包括传递依赖方)时,项目筛选才安全。
确认根配置枚举了所有相关项目,并且跨包导入在依赖图中可见。文件系统读取、子进程目标、生成的输出、模板、通过代理获取的依赖、仅类型关系、文件扫描型仓库守护检查、浏览器模式测试以及基于 Docker 的测试层,可能需要仓库专用的触发器或单独的命令。保留已配置的 watchTriggerPatterns 和 forceRerunTriggers。重启/重新播种只能修复过时或新近在依赖图中可见的关系;对于依赖图无法表示的依赖,应配置显式映射或运行负责的测试套件/测试层。
Watch 模式是内部循环加速器,不是 PR 证据。PR 前记录必须来自针对最终代码树完成的一次非 watch 全量运行。
仓库规则
仓库专属的测试、覆盖率、变异测试和证据时效规则优先。仅当目标仓库未定义更严格的失效规则时,才使用此发行版的时效模型(panel-review 技能的 references/pr-readiness.md)。在生产代码或适用的测试发生后续更改时,如果仓库规则会使证据失效,则此全局指南绝不能弱化该规则。
RED-GREEN-REFACTOR 开发周期
RED:先编写失败测试
- 对于新增或变更的行为,在获得失败的行为测试之前,禁止编写生产代码
- 测试应描述期望的行为,而不是实现方式
- 测试应因正确的原因而失败
- 运行能体现这一 RED 阶段的范围最窄的测试选择器;不要在快速迭代中运行无关测试
- 完成测试前,根据变异规则检查预期行为:边界、布尔组合、相等性、算术恒等式、数组/字符串操作、可选链和副作用
- 立即补上明显缺失的用例;如果预期行为取决于产品/领域判断,则使用测试框架的提问功能
GREEN:只编写通过测试所需的最少代码
- 只编写足以让测试通过的代码
- 不要添加测试未要求的功能
- 造假是合理的:如果硬编码返回值就能通过测试,那就先这样做,再通过三角验证——添加第二个测试用例,迫使实现采用真实逻辑。只有测试要求时才进行泛化
- 使用当前聚焦的监视器,或相关/受影响的一次性命令;只有在聚焦行为通过后才扩大范围
REFACTOR:评估改进
- GREEN 阶段通过后,使用通过的行为测试作为工作安全网,评估改进
- 仅在适合重构时加载
refactoring技能;否则记录N/A - 如果工作流使用提交作为安全检查点,则在重构前获得对当前基线提交的批准
- 每次小规模重构后,确保聚焦测试和受影响测试仍然通过;在 PR 前检查关卡运行完整测试套件
根据需要重复 RED-GREEN-REFACTOR,直到完成当前阶段的 PR 范围。不要在每次增量、重构或提交后运行自动化变异测试框架。无论是否运行该框架,最终回复都必须用一行说明变异测试关卡的结果——Mutation: ran, X killed / Y survived (score Z%)、Mutation: N/A — <rationale> ; alternate evidence: <what> 或 Mutation: deferred to the PR gate — this slice is not yet PR-ready。未说明的关卡等同于未记录的关卡。
阶段结束时的 PR 就绪关卡
实现和重构阶段完成,且其他方面都已准备好提交 PR 后,运行一次此关卡:
- 运行变异测试或提供替代证据:在有意义时,针对累积的分支/PR 范围运行
mutation-testing,并给出 killed/survived/score 报告。否则,明确记录N/A的理由,以及相称的可达性、配置、契约、集成或运行证据。 - 适用时消除变异项:为有价值的存活变异项添加或强化行为测试,直接修复明显缺漏;如果某个变异项的价值不明确,则询问人工审核者。
- 在关卡内重新运行:处理存活变异项时,使用聚焦的变异测试;随后重新运行分支差异命令。这些重新运行属于同一个 PR 关卡;它们不会让变异测试重新成为每个 TDD 增量的必做项。
- 验证:完成剩余的 PR 检查,并确保所有测试通过。绝不要为了凑流程而臆造结构性变异项。
证据、历史记录与覆盖率
记录预期的 RED 失败、GREEN 通过以及最终的非监听模式验证,并在最终回复中说明这三项:指出 RED 运行(使用的选择器及其产生的失败,例如 pinNote is not a function)、GREEN 运行以及已完成的非监听模式完整运行。只报告通过数量并不算提供了 TDD 证据。现有提交历史可以佐证这一过程,但不要为了作秀式地执行 TDD 而重塑提交;请遵循代码库的交付政策。
覆盖率是诊断指标,不是通用目标。政策要求时,或在声称覆盖率时,运行代码库的覆盖率命令。核实所声称的具体行、分支、语句和函数,然后判断是否有任何缺口代表未经测试的行为。高百分比并不能证明测试质量;请使用 testing 技能中的覆盖率作秀检查,并酌情采用变异测试或其他佐证。
开发工作流
添加新功能
- 遵守代码库的起始要求——如有要求,在首次编辑前启动经过验证的监视器;否则,在 RED 后立即启动
- 编写失败的测试——描述预期行为
- 开始或观察聚焦反馈——优先使用代码库自带的监视器;否则,仅在已通过规范实时验证、确认已安装的版本和配置后,使用基于差异选择测试的 Vitest 监视器,或使用受影响测试的一次性等效命令;确认测试因预期原因失败
- 实现最小改动——只实现通过测试所需的内容
- 运行聚焦/受影响的测试——确认行为及相关测试通过
- 酌情进行有价值的重构——在聚焦和受影响的测试保持通过的同时,改进代码结构
- 重复 RED-GREEN-REFACTOR——持续进行,直到计划中的 PR 范围全部完成;期间不要运行变异测试工具
- 达到 PR 就绪状态后,运行一次变异测试门禁——停止监视器,在有意义时运行变异测试(否则明确记录
N/A并提供适度的替代证据),在该门禁内处理有价值的存活变异,并完成代码库定义的非监听模式 PR 检查。遵循目标代码库的证据失效规则;只有在不存在更严格的代码库规则时,才使用本发行版的新鲜度模型(panel-review技能,references/pr-readiness.md)
工作流示例
# 1. If the repository requires its watcher before the first edit, start it now:
# pnpm test:watch
# 2. Write failing test
it('should reject empty user names', () => {
const result = createUser({ id: 'user-123', name: '' });
expect(result.success).toBe(false);
}); # ❌ Test fails (no implementation)
# 3. If it is not already running, start the tested repository watcher now.
# Starting after RED makes the expected initial execution easy to verify when
# the repository does not mandate an earlier start.
pnpm test:watch
# If no repository watcher exists and this Vitest version/configuration has
# passed the live proof:
pnpm exec vitest --changed origin/main --watch
# Or use an affected one-shot:
pnpm exec vitest --changed origin/main --run
# 4. Confirm the expected RED test executed, then implement minimum code
if (user.name === '') {
return { success: false, error: 'Name required' };
} # ✅ Test passes
# 5. Refactor if needed (extract validation, improve naming)
# 6. Repeat RED-GREEN-REFACTOR increments without running the mutation harness
# 7. After the final edit, stop the watcher with Ctrl+C. When the accumulated work
# is otherwise ready to create the PR, run the
# end-of-phase mutation gate once and address valuable survivors within it.
# 8. Exit watch mode and finish the remaining PR verification, including the
# inspected repository-defined complete non-watch test gate.
# If later verification changes mutation-relevant production or applicable
# tests/evidence, apply the target repository's invalidation rule and rerun
# the evidence it invalidates; use this distribution's review
# pr-readiness freshness model only when no stricter rule exists. Keep
# final-tree verification current.
拉取请求要求
提交 PR 前:
- 所有代码库要求的测试均通过
- 所有代码库要求的 lint 和类型检查均通过
- 使用代码库自己的命令核实覆盖率声明和代码库阈值
- 在有意义时,针对累计的 PR 范围运行一次阶段结束时的变异测试门禁,并处理有价值的存活变异;否则,明确记录
N/A并提供适度的替代证据 - 变异测试或替代证据符合目标代码库的失效规则;全局复用指南不得削弱更严格的代码库政策
- 已停止所有监视器,并通过代码库定义的完整非监听模式 PR 测试门禁;在 monorepo 中,该门禁包含所有已配置项目以及必需的集成/E2E 测试套件
- PR 范围内聚焦一致,并遵循代码库的交付政策
- 包含行为描述(而非实现细节)
PR 描述示例:
## Summary
Adds support for user role-based permissions with configurable access levels.
## Behavior Changes
- Users can now have multiple roles with fine-grained permissions
- Permission check via `hasPermission(user, resource, action)`
- Default role assigned if not specified
## Test Evidence
✅ 42/42 tests passing
✅ Repository coverage gate passed (see attached report)
## TDD Evidence
RED: expected permission test failed before production changes
GREEN: focused permission tests passed after the minimum implementation
REFACTOR: affected tests remained green after permission resolution was simplified
重构优先级
GREEN 确立通过的行为测试基线后,在适用的重构中评估并分类改进机会。优先级分类表和详细方法请加载 refactoring 技能——相关指导由该技能负责。变异测试会在阶段结束时的 PR 就绪门禁中验证累计结果。
应避免的反模式
- ❌ 编写新的或已更改的生产行为,却没有失败的行为测试
- ❌ 为实现形式编造失败测试,以此证明保留行为的重构是合理的
- ❌ 测试实现细节(对内部方法使用间谍)
- ❌ 机械地为每个实现文件都配一个测试文件
- ❌ 使用共享的可变测试状态或生命周期钩子,却没有可靠的隔离和清理
- ❌ 未经验证就相信覆盖率声明
- ❌ 模拟被测试的函数
- ❌ 在测试文件中重复定义由生产代码维护的契约架构
- ❌ 工厂函数悄悄返回不符合场景需求的对象;有意构造的无效测试数据必须在名称和类型中明确标示
- ❌ 编写推测性代码(没有测试支撑的“以防万一”逻辑)
- ❌ 每次编辑后都重新运行完整测试套件,而不是使用聚焦的监听模式或相关/受影响测试选择
- ❌ 当运行器可以根据完整变更集推导所有受影响的测试时,却在 GREEN/REFACTOR 阶段手动挑选一个测试文件
- ❌ 在监听器启动时固定精确的测试文件列表,导致新建的测试无法加入循环
- ❌ 将收集到的测试数为零或
--passWithNoTests当作 RED/GREEN 通过的证据 - ❌ 将监听模式的结果视为 PR 前完整测试套件门禁已完成
- ❌ 以为
--changed --watch会在每次保存后重新计算 VCS 影响范围,或以为后续重跑仅限于首次按 VCS 选出的测试 - ❌ 循环、超时或交接后仍让监听器或其子进程继续运行
- ❌ 在 CI、钩子或其他有限时长命令中调用监听模式,但有界且经验证可清理的实时验证框架内除外
有关测试反模式的详细信息,请加载 testing 技能。
总结检查清单
标记工作完成前:
- 每项新增或变更的行为都有要求实现该行为的失败行为测试,或有明确证据表明该变更是保留行为的 REFACTOR 切片
- 已记录 RED、GREEN 和最终验证的证据;仅当仓库以此方式使用提交历史时,它才可作为辅助证据
- 内层循环中的聚焦测试和受影响测试始终通过
- 每项声称的 RED/GREEN 结果都列明了一个确实被收集并执行的预期测试
- 已停止监听器,且没有遗留监听器进程或临时测试夹具
- PR 前已完成并通过一次非监听模式的完整测试套件运行
- 使用仓库指定的命令验证了任何覆盖率声明或仓库阈值
- 最终回复说明了变更测试门禁的结果——针对累计范围运行了一次并处理了有价值的存活项;明确标注
N/A并提供相称的替代证据;或明确说明由于此切片尚未达到 PR 就绪状态,将其推迟到 PR 门禁;绝不可不予说明 - 测试状态已隔离;在有助于提高明确性的情况下使用了测试夹具或工厂函数
- 测试验证的是行为(而非实现细节)
- 在适用时评估了重构,并在有价值时予以实施;否则明确标注
N/A