首页
/ rtk 的 Rust TDD 工作流:Red-Green-Refactor 循环与仓库级测试规范解析

rtk 的 Rust TDD 工作流:Red-Green-Refactor 循环与仓库级测试规范解析

2026-09-04 09:43:09作者:虞亚竹Luna

本文基于 rtk(Rust Token Killer)仓库中的 Claude Code 技能文档 .claude/skills/rtk-tdd/SKILL.md 展开,系统讲解该项目如何为 Rust 开发强制推行 TDD(Red-Green-Refactor):从"三条 TDD 定律"、逐步的红绿循环命令,到 Rust 惯用测试模式、命名规范、纯 TDD 的适用边界与提交前质量门禁。读完本文,你能把这套完整可执行的 TDD 流程直接套用到 rtk 或任意 Rust 项目的开发中,并理解其背后由 CLAUDE.md.claude/rules/cli-testing.md 与真实源码共同支撑的工程化闭环。

技能定位:一份自动触发的 TDD 执行手册

rtk-tdd 是 rtk 仓库内置在 .claude/skills/rtk-tdd/SKILL.md 中的 Claude Code 技能(Skill)。其 YAML frontmatter 定义如下:

name: rtk-tdd
description: >
  Enforces TDD (Red-Green-Refactor) for Rust development. Auto-triggers on
  implementation, testing, refactoring, and bug fixing tasks. Provides
  Rust-idiomatic testing patterns with anyhow/thiserror, cfg(test), and
  Arrange-Act-Assert workflow.
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash
effort: medium
tags: [tdd, testing, rust, red-green-refactor, rtk]

从这份元信息可以读出三个设计意图:

  • 自动触发场景明确:实施功能、写测试、重构、修 bug 四类任务命中后,Agent 会按本文档流程执行,而不是自由发挥;
  • 工具白名单收敛allowed-tools 只开放 ReadWriteEditBash 四项,即"读代码 + 改代码 + 跑测试"的最小权限集;
  • 执行强度标注effort: medium 表明该技能按中等强度执行,配合 tags 中的 red-green-refactor 让技能体系可被检索归类。

该技能不是孤立存在的:同目录下还有配套的参考文件 .claude/skills/rtk-tdd/references/testing-patterns.md,提供 RTK 仓库专属的测试模式样例与"未测试模块待办清单";仓库根 .claude/skills/tdd-rust/SKILL.md 则是面向"新增过滤器"场景的姊妹技能(多了 fixture 捕获、insta 快照与 token 节省率断言环节)。两套技能一通用一专用,构成本仓库的 TDD 方法论层。

TDD 三条定律:先失败,再最小化

文档开篇即给出本仓库的"三条 TDD 定律",这是整个工作流的宪法性约束:

  1. 没有失败的测试,不许写生产代码(Do NOT write production code without a failing test);
  2. 只写刚好能失败的测试(Write only enough test to fail,包括编译失败也算失败);
  3. 只写刚好能让失败测试通过的生产代码(Write only enough production code to pass the failing test)。

三条定律对应固定的循环:RED(测试失败)→ GREEN(以最小代码通过)→ REFACTOR(清理并全量回归 cargo test)。其中第 2、3 条的"just enough"是关键:测试不追求一次覆盖所有边界,实现不追求一次做到优雅——把复杂度分摊到 REFACTOR 阶段,保证每次提交都只包含一个已验证的行为增量。

Red-Green-Refactor 六步操作

文档将循环展开为可直接执行的六步,每一步都带明确的 cargo 命令与验证条件:

1. 在同一文件的 #[cfg(test)] mod tests 中写测试
2. cargo test MODULE::tests::test_name  -- 必须失败(red)
3. 在函数中实现最小逻辑
4. cargo test MODULE::tests::test_name  -- 必须通过(green)
5. 按需重构,重跑 cargo test(保持 green)
6. cargo fmt && cargo clippy --all-targets && cargo test  (最终门禁)

几个要点值得强调:

  • 测试与实现同文件。Rust 惯用做法是把 #[cfg(test)] mod tests 放在被测模块的同一 .rs 文件底部,通过 use super::*; 直接访问私有函数——这正是 references/testing-patterns.md 中所有样例模板的写法。
  • 第 2 步不可跳过。文档原文警告:"Never skip step 2. If the test passes immediately, it tests nothing."——一个不经过红灯阶段的测试,无法证明它在守护新行为,很可能是对既有行为的重复断言。"编译失败也算失败"这一点也很务实:先引用尚不存在的函数(如 filter_xxx)写测试,cargo test 直接报编译错误,这就是合法的 RED。
  • 第 6 步是最终门禁,对应文档"Pre-Commit Gate"一节(见后文),三条命令全部通过才允许进入下一步或提交。

以 rtk 仓库的实际代码为例,src/core/utils.rs 中的纯函数 truncate 就是这套流程的标准受益者——其上方 doc 注释里直接内嵌了 doctest:

/// use rtk::utils::truncate;
/// assert_eq!(truncate("hello world", 8), "hello...");
/// assert_eq!(truncate("hi", 10), "hi");

这正对应文档中"Pure function (str -> str) → 输入字面量、断言输出"的测试模式(见下节),cargo test 会把 doctest 与单元测试一并执行,属于 GREEN 阶段的最低成本验证。

Rust 惯用测试模式(Idiomatic Patterns)

文档第一张模式表定义了"任何测试的基座 + 常用断言手段":

模式 用途 使用时机
Arrange-Act-Assert 每个测试的基本结构 始终
assert_eq! / assert! 直接比较 / 布尔断言 确定性值
assert!(result.is_err()) 错误路径测试 非法输入
Result<()> 返回类型 测试中使用 ? 操作符 可失败(fallible)函数
#[should_panic] 预期 panic 不变量、前置条件
tempfile::NamedTempFile 文件/I/O 测试 依赖文件系统的代码

第二张表则按代码类型给出"测什么、怎么断言"的速查:

代码类型 测试模式 示例
纯函数 (str -> str) 输入字面量 → 断言输出 assert_eq!(truncate("hello", 3), "...")
解析/过滤 原始字符串 → 过滤 → contains / not-contains assert!(filter(raw).contains("expected"))
校验/安全 边界输入 → 断言 bool assert!(!is_valid("../etc/passwd"))
错误处理 坏输入 → is_err() assert!(parse("garbage").is_err())
结构体/枚举 roundtrip 构造 → 序列化 → 反序列化 → 比较 assert_eq!(from_str(to_str(x)), x)

这些模式与 rtk 仓库的实际测试高度一致。.claude/rules/cli-testing.md 把"过滤器函数测试"列为最高优先级(🔴 Critical),要求测试块与被测过滤器同文件,并对每个新过滤器覆盖"常规情况 + 至少一个边界情况(空输入、错误输出)";其 fixture 策略也印证了模式表中"解析/过滤"一行的落地方式——小型用例用内联字面量,输出较大或格式敏感时用 include_str! 引入 tests/fixtures/ 中的真实命令输出(例如 tests/fixtures/mvn_test_pass_slice_raw.txttests/fixtures/gradlew_build_raw.txt),src/cmds/jvm/mvn_cmd.rs 是该 fixture 模式的参考实现。

测试命名规范

文档规定了两类命名模板:

test_{function}_{scenario}
test_{function}_{input_type}

示例:test_truncate_edge_casetest_parse_invalid_inputtest_filter_empty_string

这套命名的实际收益在于:cargo test test_truncate 这类前缀过滤可以精确圈定某函数的全部测试;{scenario} / {input_type} 后缀让"哪个输入、验了什么行为"在测试列表里自解释。配套的 references/testing-patterns.md 还给出了标准骨架模板,约定每个测试模块至少包含 happy_pathempty_inputedge_case 三类用例(边界条件包括超长输入、特殊字符、Unicode)。

何时不该用纯 TDD

文档专设一节划定了纯 TDD 的适用边界——这是很多 TDD 教程容易忽略的部分,直接列出了 rtk 这类 CLI 代理项目中最常见的四类"不适合"场景及处置方式:

  • 调用 Command::new() 的函数 → 测试解析逻辑(parser),不测进程执行。rtk 的过滤器本质是"执行外部命令 + 压缩输出",执行侧依赖系统环境,解析侧才是纯函数;
  • std::process::exit() → 先把函数重构为返回 Result,再对 Result 做测试。这与 CLAUDE.md 中"Exit code propagation"规则(子进程失败时透传退出码)是配套的:run() 层负责退出码传播,filter_() 层保持可测;
  • 直接 I/O(SQLite、网络) → 用 tempfile / mock,或把纯逻辑拆出来单独测。技能参考文件 references/testing-patterns.md 明确将 tracking.rs(SQLite 统计)列为"需要 tempfile"的中优先级模块,把 container.rsfind_cmd.rs 等重 I/O 模块列为低优先级;
  • Main / CLI 装配层 → 交给集成/冒烟测试。对应 CLAUDE.mdbash scripts/test-all.sh(冒烟测试)与 .claude/rules/cli-testing.mdtests/*.rs 顶层集成测试(含 #[ignore] 的真实进程用例)的分工。

从源码结构看,这一边界在 rtk 的代码组织中有清晰投影:src/core/src/cmds/*/ 中的 filter_*truncateis_valid_* 等纯函数是 TDD 主战场,而 main.rs 的 Clap 命令路由与 run() 中的 Command 执行则归入集成测试层。

提交前门禁(Pre-Commit Gate)

文档最后给出三条硬性门禁命令:

cargo fmt --all --check
cargo clippy --all-targets
cargo test

原文要求:"All 3 must pass. No exceptions. No #[allow(...)] without documented justification."——三条全过、无例外,且不允许无书面理由的 #[allow(...)] 豁免。

这套门禁在仓库中有三处呼应:

  1. CLAUDE.md 的 "Build Verification (Mandatory)":任何 Rust 文件编辑后必须跑完整质量管线,且"fix ALL clippy warnings before moving on(零容忍)";
  2. .claude/hooks/bash/pre-commit-format.sh:挂在 git commit 的 PreToolUse 钩子,自动执行 cargo fmt --all,并运行 cargo clippy --all-targets——出现 error: 即阻断提交(警告允许通过);
  3. .claude/rules/cli-testing.md 的 "Testing Checklist":在门禁之外补充了 token 节省率 ≥60% 验证、跨平台 shell 转义测试、cargo test --ignored 集成测试等发布前检查项。

也就是说,SKILL.md 中的"Pre-Commit Gate"不是孤立的口号,而是与钩子脚本、规则文件共同组成的分层防线:技能文档约束 Agent 的行为,钩子脚本约束 git 提交动作,规则文件补充质量维度。

延伸阅读:把技能放回仓库证据链

若要在 rtk 仓库内继续深入,以下路径构成完整的证据链:

  • src/core/utils.rstruncatestrip_ansiformat_tokensformat_usd 等纯函数,每个都带 doctest,是"纯函数 TDD 模式"的现成范本;
  • references/testing-patterns.md:四类 RTK 专属测试模式(Filter / Pure Computation / Validation / ANSI Stripping)+ 未测试模块待办清单(按可测性分级:纯函数优先、重 I/O 最后);
  • .claude/skills/tdd-rust/SKILL.md:面向新增过滤器场景的强化版 TDD 流程——真实 fixture 捕获、count_tokens 节省率断言、insta 快照锁定输出格式;
  • .claude/rules/cli-testing.md:单元测试 / 集成测试 / 性能测试(<10ms 启动、<5MB 内存)的完整策略与反模式清单;
  • tests/fixtures/:真实命令输出的回归夹具库,配合 include_str! 使用。

小结

rtk-tdd 技能把 TDD 从"理念"压缩成了一张可机械执行的卡片:三条定律划定行为底线,六步命令给出精确操作序列,两张模式表覆盖 Rust 常见代码类型,命名规范让测试可检索,"何时不适用"一节避免教条主义,三条门禁命令守住提交质量。对于以 Rust 编写的 CLI 工具项目而言,这套"文档即流程、流程即门禁"的 TDD 组织方式——技能文件定义循环、钩子脚本落地拦截、规则文件补齐质量维度——是一个可以直接借鉴的完整参照。

登录后查看全文
热门项目推荐
相关项目推荐