mcpField
T
tdd

tdd

github.com/glebis测试与质量403256 次安装3 天前更新
代码仓库

关于 tdd

tdd 是一种通过多个 Agent 协作执行测试驱动开发的技能,适用于希望先编写测试再实现功能的开发流程。它用独立的测试编写、实现和重构 Agent 隔离规格与实现上下文,并支持交互、自动、恢复和试运行模式。

主要功能

  1. 01按 RED-GREEN-REFACTOR 循环编排
  2. 02测试编写与实现 Agent 分离
  3. 03按架构层次拆分垂直切片
  4. 04支持交互、自动与恢复模式
  5. 05试运行时渲染提示词并验证流程

适用场景

  1. 01为新功能先建立测试覆盖
  2. 02先编写复现测试再修复缺陷
  3. 03检查 TDD 脚本和提示词而不写代码

安装

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

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

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

包含的文件

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

README.md
.claude-plugin/
plugin.json
references/
agent_prompts.md
anti_patterns.md
framework_configs.md
layer_guide.md
scripts/
discover_docs.sh
extract_api.sh
run_tests.sh
tests/
conftest.py
test_discover_docs.py
test_extract_api.py
test_prompts.py
test_run_tests.py
tests/fixtures/go_project/
calculator.go
tests/fixtures/python_project/
README.md
tests/fixtures/python_project/docs/
API.md
openapi.yaml
tests/fixtures/python_project/src/
calculator.py
models.py
tests/fixtures/ts_project/
README.md
tests/fixtures/ts_project/src/
calculator.ts
tests/snapshots/
implementer.txt
refactorer.txt
test_writer.txt

详细介绍

测试驱动开发 — 多 Agent 编排

使用独立的子 Agent分别编写测试和实现,严格执行 RED-GREEN-REFACTOR 循环。核心创新:**测试编写者永远看不到实现代码,实现者永远看不到规格说明。**这可以防止 LLM 将实现意图泄露到测试设计中。

适用场景

  • 用户要求采用 TDD、测试优先或 red-green-refactor 工作流
  • 用户提供功能描述或 Bug 报告,并输入 /tdd
  • 用户希望添加功能,并从一开始就确保有测试覆盖
  • 用户希望先编写可复现 Bug 的测试,再修复 Bug

调用模式

调用方式行为
/tdd <feature>交互模式 — 在各个切片和每个 RED 检查点暂停并等待批准
/tdd --auto <feature>自主模式 — 不暂停,运行所有切片;仅在遇到不可恢复错误时停止
/tdd --resume从项目根目录的 .tdd-state.json 恢复
/tdd --dry-run <feature>验证模式 — 完整运行 Phase 0 + Phase 1,渲染所有提示词,但跳过 Task() 调用。不写入代码。

在 --auto 模式下,跳过所有 [HUMAN CHECKPOINT] 步骤。改为打印状态行:

[auto] RED  slice 1/4: "validates email format" — test failing as expected
[auto] GREEN slice 1/4: passing (attempt 1)
[auto] REFACTOR slice 1/4: 1 suggestion applied, 0 skipped

仅在以下情况下停止并询问用户:

  • 经过 5 次尝试后,实现仍然失败
  • 经过 3 次尝试后,仍无法自动修复回归问题
  • 脚本错误导致无法继续(缺少二进制文件、权限被拒绝等)

在 --dry-run 模式下,验证完整的编排流程,但不执行任何子 Agent,也不写入任何代码:

  1. 完整运行 Phase 0:检测框架、验证基线、提取 API、发现文档、创建状态文件
  2. 完整运行 Phase 1:分解为多个切片(仍需用户批准)
  3. 针对每个切片:使用实际变量渲染全部三份 Agent 提示词(测试编写者、实现者、重构者)。向用户打印渲染后的提示词及其字符数。
  4. 不调用 Task()。不写入测试文件。不生成实现代码。
  5. 验证:检查所有模板变量是否已解析(不存在 {UNRESOLVED} 占位符)、所有脚本能否无错误执行,以及状态文件格式是否正确。
  6. 报告摘要:
DRY RUN COMPLETE: {feature name}

Phase 0:
  Framework: {framework}
  Language: {language}
  Baseline: {pass|greenfield}
  API surface: {line count} lines
  Doc context: {line count} lines (or "none")

Phase 1:
  Slices: {N} ({layer breakdown})

Prompts rendered: {N * 3} (all variables resolved)
  Test Writer:   {char count} chars
  Implementer:   {char count} chars
  Refactorer:    {char count} chars

State file: .tdd-state.json written
No code was modified.

此模式适用于:

  • 验证脚本能否在项目环境中正常运行
  • 在决定完整运行 TDD 流程前,检查提示词内容
  • 在不产生副作用的情况下测试技能变更

架构概览

ORCHESTRATOR (you, reading this file)
├─ Phase 0: Setup — detect framework, extract API, create state file
├─ Phase 1: Decompose into vertical slices → user approves
│
├─ FOR EACH SLICE:
│   ├─ Phase 2 (RED):    Task(Test Writer)  ← spec + API only
│   ├─ Phase 3 (GREEN):  Task(Implementer)  ← failing test + error only
│   └─ Phase 4 (REFACTOR): Task(Refactorer) ← all code + green results
│
└─ Summary
上下文边界(关键约束)
Agent可见内容不可见内容
测试编写者切片规格、公共 API 签名、框架约定、分层约束实现代码、其他切片、实现计划
实现者失败的测试代码、测试失败输出、文件树、现有源代码、分层约束原始规格、切片描述、未来计划
重构者所有实现代码 + 所有测试 + 通过测试的结果、涉及的分层原始规格、分解依据

工作流

阶段 0:设置(每个会话执行一次)

步骤 1:检测框架和测试运行器。

Check for: package.json (jest/vitest), pyproject.toml/pytest.ini (pytest),
go.mod (go test), Cargo.toml (cargo test), Gemfile (rspec), composer.json (phpunit)

如果无法确定,请询问:“运行测试的命令是什么?(例如,npm test、pytest)”

步骤 2:从源文件检测语言(用于 Agent 提示词):

TypeScript (.ts/.tsx), JavaScript (.js/.jsx), Python (.py), Go (.go), Rust (.rs), Ruby (.rb), PHP (.php)

步骤 3:验证基线是否通过。

bash ~/.claude/skills/tdd/scripts/run_tests.sh {FRAMEWORK} "{TEST_COMMAND}"

解析 JSON 输出。

  • 如果 status 为 "pass":继续。
  • 如果 status 为 "fail":停止——“现有测试未通过。TDD 应从通过测试的基线开始。”
  • 如果 status 为 "error" 且 total 为 0:绿地项目——目前还没有测试。这没有问题。继续。

步骤 4:提取公共 API 接口。

bash ~/.claude/skills/tdd/scripts/extract_api.sh {SOURCE_DIR}

保存输出——测试编写者会看到这些内容。如果输出为空(绿地项目),这是预期情况。

步骤 5:发现项目文档。

bash ~/.claude/skills/tdd/scripts/discover_docs.sh {PROJECT_ROOT} --lang {LANGUAGE}

此操作会搜索:

  • 文档文件:README、ARCHITECTURE.md、docs/ 文件夹、DESIGN.md、SPEC 文件、ADR
  • API 规范:OpenAPI/Swagger、GraphQL 架构、.proto 文件
  • 源代码文档字符串:JSDoc、Python 文档字符串、Go 文档注释、Rust /// 注释

将输出保存为 {DOC_CONTEXT}。这些内容将用于:

  • 阶段 1——确保切片分解参考文档中说明的行为和 API 契约
  • 阶段 2——确保测试编写者编写的测试符合文档中的意图,而不只是代码签名

如果没有输出(未找到文档),也没有问题——不使用文档上下文,继续即可。

步骤 6:在项目根目录中创建状态文件 .tdd-state.json:

{
  "feature": "user's feature description",
  "framework": "jest|vitest|pytest|go|cargo|rspec|phpunit",
  "language": "typescript|javascript|python|go|rust|ruby|php",
  "test_command": "the full test command",
  "source_dir": "src/",
  "doc_context": "output from discover_docs.sh (or empty string)",
  "auto_mode": false,
  "dry_run": false,
  "slices": [],
  "current_slice": 0,
  "phase": "setup",
  "layer_map": {},
  "files_modified": [],
  "test_files_created": []
}

slices 数组中的每个切片都包含一个 layer 字段:"domain"、"domain-service"、"application" 或 "infrastructure"。有关如何分配层级,请参阅阶段 1。

layer_map 将目录前缀映射到层级。此映射在阶段 1 根据项目结构构建:

{
  "layer_map": {
    "src/domain/": "domain",
    "src/services/": "domain-service",
    "src/application/": "application",
    "src/infrastructure/": "infrastructure",
    "src/adapters/": "infrastructure",
    "src/controllers/": "infrastructure"
  }
}

如果项目没有明确的目录与层级映射(扁平结构),将 layer_map 设为 {},并跳过基于路径的验证。

步骤 5a(自动检测 layer_map):如果 layer_map 为空,请扫描源目录,查找常见的 DDD/分层架构目录名称,并自动填充:

Common directory → layer mappings (check if directories exist):
  */domain/       → "domain"
  */models/       → "domain"          (ORM models often serve as domain entities)
  */entities/     → "domain"
  */value_objects/ → "domain"
  */services/     → "application"     (unless clearly infrastructure)
  */application/  → "application"
  */use_cases/    → "application"
  */core/         → "application"
  */infrastructure/ → "infrastructure"
  */adapters/     → "infrastructure"
  */controllers/  → "infrastructure"
  */api/          → "infrastructure"
  */bot/          → "infrastructure"  (Telegram/Discord bot handlers)
  */handlers/     → "infrastructure"
  */repositories/ → "infrastructure"  (concrete repo implementations)

仅为源代码树中实际存在的目录添加条目。如果匹配的目录少于 2 个,则将 layer_map 留空(扁平项目)。将自动检测到的映射呈现给用户确认:

Auto-detected layer map from directory structure:
  src/models/     → domain
  src/services/   → application
  src/core/       → application
  src/bot/        → infrastructure
  src/api/        → infrastructure

Does this mapping look correct? (adjust if needed)

更新状态:设置为 "phase": "setup"。立即写入状态文件。


阶段 1:规范分解

获取用户的功能需求,并将其分解为按顺序排列的垂直切片。每个切片对应一种可测试的行为。

使用文档上下文:分解时,请交叉参考阶段 0 步骤 5 中的 {DOC_CONTEXT}。文档通常会描述预期行为、边缘情况和 API 契约,这些内容有助于确定切片边界。如果文档提到了具体错误情况、验证规则或行为要求,请考虑将其作为切片候选。

从内到外排列切片

确定所有切片后,按架构层级从内到外排序。这样可确保每个切片都能基于之前切片中的真实(而非模拟)实现构建:

  1. 领域模型切片优先——纯逻辑,无依赖,无需模拟
  2. 领域服务切片——使用真实领域对象执行跨聚合操作
  3. 应用服务/用例切片——使用内存中的端口伪实现进行编排
  4. 基础设施适配器切片最后——存储库、外部 API、框架适配器

为每个切片分配一个 layer 标签:domain、domain-service、application 或 infrastructure。使用 references/layer_guide.md 中的启发式规则进行分类。

**为什么要从内到外?**领域切片会生成后续切片可直接使用的真实对象。这能最大限度地减少模拟,并尽早发现集成问题。此外,这也确保在做出任何基础设施决策之前,业务规则已经实现并经过测试。

对于所有代码都位于同一层的简单项目,所有切片的 layer 都设为 "application",排序保持不变——此指导原则会自然适配这类项目。

切片排序中的边界情况

仅涉及基础设施的功能(例如,“添加电子邮件提供商重试逻辑”“从 Postgres 切换到 MySQL”):

  • 如果某项功能没有领域或应用层行为变更,则所有切片都可以标记为 infrastructure。这是有效的——可完全跳过内层。
  • 呈现为:“这是纯基础设施变更。所有切片都属于基础设施层。”

缺少端口接口(领域服务需要一个尚不存在的端口):

  • 第一个需要该端口的切片应在实现过程中创建此接口。实现者可以在内层创建文件(领域/领域服务可以定义自己的端口)。
  • 示例:RegistrationService 的 domain-service 切片会在 GREEN 阶段创建 domain/ports/UserRepository 接口。

横切切片(切片涉及多个层):

  • 标记为其涉及的最内层。实现者可以在该层及任何更内层创建文件。
  • 示例:同时引入新领域事件的用例标记为 application,但会在 domain/events/ 中创建文件。

向用户呈现:

I've broken this into N vertical slices (ordered inside-out):

Domain:
1. [behavior] — [what the test verifies]

Domain Services:
2. [behavior] — [what the test verifies]

Application:
3. [behavior] — [what the test verifies]

Infrastructure:
4. [behavior] — [what the test verifies]

Each slice follows RED -> GREEN -> REFACTOR before moving to the next.
Does this decomposition look right?

如果所有切片都属于同一层,则跳过层级标题,直接以平面列表呈现。

等待用户批准(即使在 --auto 模式下也必须如此——切片分解始终需要确认)。

更新状态:写入切片数组(每个切片都包含 layer 字段),并设置 "phase": "decomposed"。


Dry-Run 阶段覆盖(阶段 2–4)

在 --dry-run 模式下,完全替换阶段 2–4,对每个切片执行以下操作:

  1. 刷新 API 概览(extract_api.sh)
  2. 填入所有变量并渲染 Test Writer 提示词。在 ### Test Writer Prompt (slice N) 标题下打印。
  3. 使用占位测试代码渲染 Implementer 提示词:将 "(dry-run: test code would be generated by Test Writer)" 用于 {FAILING_TEST_CODE},将 "(dry-run: no test output)" 用于 {TEST_FAILURE_OUTPUT}。
  4. 使用占位值渲染 Refactorer 提示词:将 "(dry-run: no green output)" 用于 {GREEN_TEST_OUTPUT},将 "(dry-run: code from Test Writer)" 用于 {ALL_TEST_CODE},将 "(dry-run: code from Implementer)" 用于 {ALL_IMPLEMENTATION_CODE}。
  5. 对每个渲染后的提示词,验证其中不再包含 {UNRESOLVED_VARIABLE} 模式(正则表达式:\{[A-Z][A-Z_]+\})。将任何未解析变量报告为错误。
  6. 打印每个提示词的字符数。
  7. 继续处理下一个切片(不调用 Task()、不写入文件、不运行测试)。

处理完所有切片后,打印 dry-run 摘要并退出。不要清理状态文件——它可用于后续的 --resume。


阶段 2:RED —— 编写一个失败测试

步骤 1:刷新 API 概览(随着切片的实现,API 概览会发生变化):

bash ~/.claude/skills/tdd/scripts/extract_api.sh {SOURCE_DIR}

步骤 2:从 references/agent_prompts.md -> “Test Writer Agent” 部分读取提示词模板。填入以下内容来构建提示词:

  • {SLICE_SPEC}:当前切片的行为描述
  • {LANGUAGE}:阶段 0 检测到的语言
  • {FRAMEWORK}:检测到的框架名称
  • {API_SURFACE}:extract_api.sh 的输出
  • {DOC_CONTEXT}:discover_docs.sh 的输出(阶段 0 步骤 5)。仅包含与当前切片相关的部分——如果完整输出过大,则按关键词匹配筛选。
  • {TEST_FILE_PATH}:测试文件应放置的位置(遵循项目约定)
  • {EXISTING_TEST_CONTENT}:测试文件的当前内容(如果存在),否则填写“尚无测试文件。”
  • {FRAMEWORK_SKELETON}:references/framework_configs.md 中相关的框架骨架
  • {LAYER}:阶段 1 中该切片的层级标签
  • {LAYER_TEST_CONSTRAINTS}:特定层的测试约束(参见 agent_prompts.md -> Layer-Specific Constraint Lookup)

步骤 3:启动 Test Writer Agent:

Task(subagent_type="general-purpose", prompt=<constructed prompt>)

步骤 4:使用 agent_prompts.md 中的 parse_agent_json 逻辑解析 JSON 响应:

  1. 如果存在 Markdown 围栏,则移除
  2. 尝试直接解析 JSON
  3. 如果失败,找到第一个 { 和最后一个 },尝试解析它们之间的子字符串
  4. 如果仍然无效:在 Task 调用后附加“Return ONLY a JSON object.”,然后重试一次
  5. 如果仍然失败:从原始响应中手动提取测试代码

步骤 5:将测试代码写入测试文件。如果文件已存在,则追加测试函数(并合并导入项)。如果是新文件,则使用 Agent 提供的 imports_needed + test_code 创建文件。

步骤 5a(写入后测试异味扫描):运行测试前,扫描测试代码中常见的异味:

异味检测方式操作
断言轮盘同一测试函数中存在多个不带消息的裸 assert 语句(3 个或以上)警告用户(不阻止):“测试有 N 个裸断言——建议添加失败消息,以便更轻松地调试。”
未知测试测试名称过于笼统:匹配 test_1、test_it、test_works、test_example、test_thing重新启动 Test Writer,并附加:“使用描述性测试名称,使其读起来像行为规范(例如,test_rejects_empty_email)。”
同义反复断言assert True、当函数不存在返回 None 的路径时使用 assert result is not None,或将 assert isinstance(result, X) 作为唯一断言重新启动 Test Writer,并附加:“该断言是同义反复——请测试实际行为/值,而不只是函数是否返回了某个值。”

步骤 5b(写入后分层检查):扫描测试代码中违反分层的模式:

层测试代码中的禁止模式
domainjest.mock(、vi.mock(、Mock(、mock.patch、unittest.mock、gomock、mockery — domain 测试不得使用模拟库
domain-servicedomain 对象的相同模拟模式(模拟 ports/repos 可以)
application无禁止模式(预期会模拟 ports)
infrastructure无禁止模式

如果发现禁止模式:

  1. 从测试中移除违规模拟/模式
  2. 重新启动 Test Writer,并附加:“不要使用模拟库。这是一个 {LAYER} 层测试。请使用真实的 domain 对象。”
  3. 如果第二次尝试仍使用禁止模式,则询问用户

步骤 6:运行测试,确认其失败(预期是断言失败,而非设置错误):

bash ~/.claude/skills/tdd/scripts/run_tests.sh {FRAMEWORK} "{TEST_COMMAND_FOR_SPECIFIC_TEST}"

步骤 7:使用语义验证评估结果:

结果操作
status: "fail",断言错误正确的 RED — 测试失败是因为预期行为尚不存在。继续。
status: "fail",ImportError / ModuleNotFoundError设置问题,不是正确的 RED。 测试甚至无法导入被测模块。修复:创建最小存根(空类/函数),使导入成功,然后重新运行。此时测试应因断言而失败。
status: "fail",缺少方法导致 AttributeError与导入错误类似 — 类存在,但方法不存在。如果断言也会失败,则这是可接受的 RED。继续。
status: "pass"行为已存在。记录:“测试通过 — 跳过该切片(已实现)。”递增 current_slice,移至下一个切片。
status: "error",SyntaxError修复:测试存在拼写错误。读取 raw_tail,直接修复测试文件。重新运行。如果尝试修复 2 次后仍然报错,则询问用户。
status: "error",编译/框架错误修复:导入错误、缺少 fixture 或框架配置错误。读取 raw_tail,直接修复测试文件。重新运行。如果尝试修复 2 次后仍然报错,则询问用户。

步骤 8(仅交互模式 — 在 --auto 下跳过):向用户展示:

RED: Test written and failing as expected.

Test: {test_name}
File: {test_file_path}
Failure: {failure message from JSON}

This test verifies: {test_description from agent response}

Proceed to GREEN phase? (or adjust the test?)

继续执行 GREEN 前,请等待用户批准。

更新状态:"phase": "red",将测试文件添加到 test_files_created。立即写入状态。


阶段 3:GREEN — 最小实现

步骤 1:阅读失败的测试文件和测试失败输出(RED 阶段 run_tests.sh 结果中的完整 raw_tail)。

步骤 2:构建源文件(不含测试文件、node_modules 等)的文件树:

find {SOURCE_DIR} -type f \( -name '*.ts' -o -name '*.js' -o -name '*.py' -o -name '*.go' -o -name '*.rs' -o -name '*.rb' -o -name '*.php' \) | grep -v test | grep -v spec | grep -v node_modules | grep -v __pycache__ | grep -v vendor | grep -v target | grep -v dist | grep -v build | head -50

步骤 3:阅读测试导入或引用的现有源文件。

步骤 4:阅读 references/agent_prompts.md 中的提示词模板 → “Implementer Agent” 部分。填写:

  • {LANGUAGE}:检测到的语言
  • {FAILING_TEST_CODE}:完整的测试文件内容
  • {TEST_FAILURE_OUTPUT}:run_tests.sh JSON 输出中的 raw_tail
  • {FILE_TREE}:步骤 2 中的源文件列表
  • {EXISTING_SOURCE}:相关源文件的内容(如有;新建项目可能为空)
  • {LAYER}:该切片在 Phase 1 中的层标签
  • {LAYER_DEPENDENCY_CONSTRAINT}:特定层的依赖约束(参见 agent_prompts.md → Layer-Specific Constraint Lookup)

重试时(attempt > 1),还要填写 {?PREVIOUS_ATTEMPT} 部分:

  • {PREVIOUS_ATTEMPT_DESCRIPTION}:失败尝试中的 explanation 字段
  • {PREVIOUS_ATTEMPT_ERROR}:失败尝试后测试运行结果中的 raw_tail

重要:不要包含切片规格、功能说明或任何未来计划。Implementer 仅依据测试进行工作。

步骤 5:启动 Implementer Agent:

Task(subagent_type="general-purpose", prompt=<constructed prompt>)

步骤 6:解析 JSON 响应。验证层边界,然后应用文件更改。

步骤 6a(层路径验证):如果 layer_map 不为空,请检查响应中的每个文件路径是否符合当前切片的层:

For each file in response.files:
  inferred_layer = lookup file.path against layer_map (longest prefix match)
  if inferred_layer exists AND inferred_layer != current_slice.layer:
    if inferred_layer is OUTER relative to current_slice.layer:
      REJECT: "Implementer created/modified {file.path} which belongs to
      the {inferred_layer} layer, but this is a {current_slice.layer} slice.
      Inner layers must not depend on outer layers."
      → Re-launch Implementer with appended constraint:
        "Do NOT create or modify files in {inferred_layer} directories.
        This slice is {current_slice.layer} only."
    if inferred_layer is INNER relative to current_slice.layer:
      ALLOW: outer layers may touch inner-layer files (e.g., adding a port interface)

“外层”检查的层级顺序:domain < domain-service < application < infrastructure。

如果 layer_map 为空(扁平项目),则跳过此验证。

步骤 6b:应用通过验证的文件更改:

对于响应 files 数组中的每个文件:

  • 如果 action 为 "create" 或 "overwrite":使用 Write 工具创建或覆盖文件,并写入完整内容
  • 如果 action 为 "edit"(用于超过 200 行的现有文件):使用 Edit 工具,通过 old_string → new_string 应用更改。Implementer 只会返回带有上下文的已更改函数——确定插入位置或要替换的函数,并相应地使用 Edit 工具。如果编辑目标不明确,则回退到读取完整文件并使用 Write。
  • 对于超过 200 行且 Implementer 仍返回完整内容的现有文件(action = "overwrite"),优先使用 Edit 工具仅应用差异,以避免意外重新格式化未更改的代码

步骤 7:运行指定测试:

bash ~/.claude/skills/tdd/scripts/run_tests.sh {FRAMEWORK} "{TEST_COMMAND_FOR_SPECIFIC_TEST}"

步骤 8:重试循环(如果测试仍然失败):

attempt = 1
max_attempts = 5
previous_explanation = null
previous_error = null

while status != "pass" AND attempt <= max_attempts:
    previous_explanation = explanation from last Implementer response
    previous_error = raw_tail from last test run

    Launch FRESH Task(Implementer) with:
      - same test code + file tree + existing source (re-read!)
      - NEW failure output
      - PREVIOUS_ATTEMPT section filled in

    Apply changes (Write tool for each file)
    Re-run test
    attempt += 1

if still failing after max_attempts:
    STOP. Present to user:
    "Implementation failed after 5 attempts. Last error: {raw_tail}"
    Ask: "Adjust the test, try a different approach, or debug manually?"

每次重试都使用全新的 Task 调用,且只提供上一次尝试的说明和错误。这可以避免 Implementer 陷入无关问题,同时为其提供足够的信息来尝试不同的策略。

步骤 9:指定测试通过后,运行完整测试套件:

bash ~/.claude/skills/tdd/scripts/run_tests.sh {FRAMEWORK} "{FULL_TEST_COMMAND}" --all

步骤 10:处理回归问题:

结果操作
全部通过继续 REFACTOR
发现回归问题自动修复:使用回归测试失败信息启动全新的 Implementer。应用更改。重新运行完整测试套件。最多重复 3 次。如果修复 3 次后仍然失败,则停止并告知用户。

步骤 11(仅交互模式——在 --auto 模式下跳过):向用户展示:

GREEN: Test passing with minimal implementation.

Implementation: {explanation from agent response}
Files changed: {list}
All tests: {passed} passing, {failed} failing

Proceed to REFACTOR phase? (or adjust?)

更新状态:"phase": "green",更新 files_modified。立即写入状态。

步骤 12(仅适用于 domain/domain-service 切片):在 REFACTOR 之前进行层纯度检查: 对于 domain 或 domain-service 层切片中的每个新增/修改文件:

  • 导入扫描:读取所有导入/require 语句。根据 layer_map 检查每个导入的模块。将任何来自外层的导入标记为违规。
  • 构造函数检查:确认构造函数不接受任何类型来自外层的参数(不允许 ORM 会话、HTTP 客户端、框架配置)
  • 静态调用检查:不得静态调用外层代码中的方法
  • 如果发现违规,请立即修复(将依赖移至端口接口),然后再进入 REFACTOR

第 13 步:全仓库导入扫描(所有层,每个切片运行一次):

扫描所有源文件(不限于本次会话修改的文件),检查依赖方向违规:

# For each source file, extract imports and check against layer_map
# Language-specific patterns:
#   Python: from X import Y, import X
#   TypeScript/JS: import ... from 'X', require('X')
#   Go: import "X"

对于每个文件:

  1. 根据 layer_map 确定其所属层(如果没有匹配项则跳过)
  2. 对于每个导入,根据 layer_map 确定被导入模块所属的层
  3. 如果被导入层相对于文件所属层位于外层 → 判定为违规

在进入 REFACTOR 前向用户报告违规:

Layer scan found N dependency direction violation(s):
- domain/user.py imports infrastructure/db.py (domain → infrastructure)
- domain/services/registration.py imports adapters/email.py (domain-service → infrastructure)

在 --auto 模式下:尝试自动修复(用端口接口替换具体导入)。在交互模式下:展示违规并询问用户如何处理。

此步骤补充了 Refactorer 的导入检查(它只检查会话文件),实现全仓库扫描。静态工具会漏掉约 23% 的违规(Pruijt et al., 2017)——结合文本与结构检查可提高覆盖率。


第 4 阶段:REFACTOR

第 1 步:收集所有上下文:

  • 本次会话期间创建/修改的所有测试文件
  • 本次会话期间修改的所有源文件
  • 绿色测试输出

第 2 步:从 references/agent_prompts.md ->“Refactorer Agent”部分读取提示词模板。填写:

  • {LANGUAGE}:检测到的语言
  • {GREEN_TEST_OUTPUT}:显示全部测试通过的完整测试输出
  • {ALL_TEST_CODE}:所有测试文件的内容
  • {ALL_IMPLEMENTATION_CODE}:所有已修改源文件的内容
  • {SLICE_LAYERS}:截至目前已完成的所有切片中,不重复层名称的逗号分隔列表

第 3 步:启动 Refactorer Agent:

Task(subagent_type="general-purpose", prompt=<constructed prompt>)

第 4 步:解析 JSON 响应。如果 suggestions 为空,则跳至第 6 步。

按优先级顺序(优先级高的在前)逐条应用建议:

对于每条建议:

  1. 应用代码更改(使用 Edit 工具,为每个文件将 old_code 替换为 new_code)
  2. 运行项目的 linter/formatter 检查(根据项目配置检测):
    • Python:python -m black --check {files} && python -m flake8 {files} && python -m mypy {files}
    • TypeScript/JS:npx eslint {files} 或 npx tsc --noEmit
    • Go:go vet ./...
    • Rust:cargo clippy
    • 如果 lint 失败 -> 立即还原并跳过此建议(与测试失败处理相同)
  3. 运行完整测试套件
  4. 如果任何测试失败 -> 立即还原(重新读取编辑前的文件并将其写回)并跳过此建议
  5. 如果所有测试和 lint 均通过 -> 保留更改

第 5 步(仅交互模式——在 --auto 模式下跳过):展示:

REFACTOR: Code improved, all tests still passing.

Applied: {list of accepted suggestions}
Skipped: {list of reverted suggestions, if any}
All tests: {count} passing

[Moving to slice N of M] or [All slices complete]

在 --auto 模式下,打印一行简短信息:

[auto] REFACTOR slice N/M: {applied_count} applied, {skipped_count} skipped

更新状态:"phase": "refactor"。立即写入状态。


第 5 阶段:下一个切片或完成

如果还有切片 -> 在状态中递增 current_slice,返回第 2 阶段。

如果所有切片均已完成 -> 展示摘要:

TDD Complete: {feature name}

Slices implemented: N
Tests written: N
Files created/modified: {list}
All tests passing: yes

清理:删除 .tdd-state.json(在 --auto 模式下静默删除;在交互模式下询问用户)。


恢复支持

用户调用 /tdd --resume 时:

  1. 从项目根目录读取 .tdd-state.json
  2. 报告当前状态:“找到功能‘{feature}’的 TDD 会话。当前位于第 {N}/{total} 个切片,阶段:{phase}。”
  3. 从当前切片的当前阶段恢复
  4. 如果状态中的 auto_mode 为 true,则继续使用自动模式

边缘情况

Greenfield 项目

没有源文件、没有测试、没有测试配置。请妥善处理:

  1. Phase 0 Step 3:如果 run_tests.sh 返回 status: "error" 且 total: 0,检查是否存在测试文件。如果没有,这就是一个 Greenfield 项目——继续执行。
  2. Phase 0 Step 4:extract_api.sh 会返回空输出。将 "(No existing API — this is a new project)" 传给 Test Writer。
  3. Phase 2:Test Writer 会从头创建测试文件。可能需要设置测试框架配置(例如 jest.config.js、pytest.ini)。如果首次运行测试时因框架错误(而非测试失败)而失败,请创建最简框架配置并重试。
Bug 修复 TDD
  1. 编写一个能复现 Bug 的测试(应失败,以证明 Bug 存在)
  2. 确认失败与所报告的 Bug 相符——人工检查点
  3. 修复:只编写让测试通过所需的最少代码(按常规进入 GREEN 阶段)
  4. 验证:没有回归问题
现有代码(特征测试)
  1. 针对当前行为编写测试(应通过——这是特征测试)
  2. 修改测试以验证期望行为(应失败)
  3. 继续执行 GREEN -> REFACTOR
用户提供的测试

如果用户提供了测试代码:

  1. 运行测试,确认其失败(确认 RED)
  2. 跳到 Phase 3(GREEN)——以用户提供的测试为准
  3. 未经询问,不要修改测试
不稳定的测试

如果测试有时通过、有时失败:停止、报告,并在继续之前修复不稳定的测试。


失败恢复参考

失败阶段恢复方法
Test Writer 返回无效 JSONRED使用去除代码围栏 + 子字符串提取的方式解析。重试一次,并附上“Return ONLY JSON.”。最后手动提取。
本应失败的测试却通过了RED记录“已实现”,跳过当前切片,继续下一个。
测试有语法/编译错误RED阅读 raw_tail,直接修复测试文件。最多重试 2 次,然后询问用户。
Implementer 返回无效 JSONGREEN使用与 Test Writer 相同的 JSON 恢复方式。
实现后测试仍然失败GREEN重试循环:携带上一次尝试的上下文,重新调用 Implementer,最多 5 次。然后询问用户。
完整测试套件出现回归问题GREEN自动修复:使用回归失败信息重新调用 Implementer。最多尝试 3 次,然后询问用户。
Refactorer 的建议导致测试失败REFACTOR立即还原,跳过该建议,继续下一个。
run_tests.sh 超时任意增加超时时间。如果仍持续超时,询问用户测试性能方面的问题。
run_tests.sh 返回 "error"任意阅读 raw_tail,查明原因。脚本错误(缺少二进制文件、路径错误)-> 修复并重试。编译错误 -> 视为实现错误。
extract_api.sh 返回空REDGreenfield 项目中的正常情况。传入“(No existing API)”消息。
Agent 的回复完全为空任意重试一次 Task 调用。如果仍然为空,询问用户。

层参考

有关层的定义、依赖规则、各层的测试策略和检测启发式,请参阅 references/layer_guide.md。

应避免的反模式

请参阅 references/anti_patterns.md。重要事项:

  • 绝不要为了让测试通过而修改测试(应修改实现,而非测试)
  • 绝不要在编写测试之前编写实现
  • 绝不要一次性编写所有测试(按纵向切片)
  • 绝不要测试实现细节
  • 绝不要跳过 RED 阶段
  • 绝不要让领域代码导入基础设施(违反依赖方向)
  • 绝不要模拟领域对象——应改为构造真实实例

框架快速参考

设置详情请参阅 references/framework_configs.md。

框架运行单个测试运行全部测试监视模式
Jestnpx jest --testPathPattern=<file> -t "<name>"npx jestnpx jest --watch
Vitestnpx vitest run <file> -t "<name>"npx vitest runnpx vitest
pytestpytest <file>::<test_name> -vpytest -vpytest-watch
Gogo test -run <TestName> ./...go test ./...—
Cargocargo test <test_name>cargo testcargo watch -x test
RSpecrspec <file>:<line>rspecguard
PHPUnitphpunit --filter <test_name>phpunit—

评论

支持 Markdown