
rtk 过滤器 TDD 工作流真实 Fixture、Token 节省断言与快照锁定的 Rust 测试六步法【免费下载链接】rtkCLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Rust binary, zero dependencies项目地址: https://gitcode.com/GitHub_Trending/rtk4/rtk本篇指南以 rtkRust Token Killer仓库中的 TDD 技能文档 SKILL.md 为核心完整拆解其为过滤器filter开发制定的 Red-Green-Refactor 测试工作流从捕获真实命令输出作为 fixture到编写 token 节省率断言、用 insta 锁定输出格式再到接入main.rs与通过质量门禁的全流程。读完本文你能够按照该仓库的标准为新命令过滤器编写可验证、可复现、且保证 ≥60% token 节省率的测试代码。工作流总览五步闭环rtk 是一个单二进制、零依赖的 Rust CLI 代理其核心职责是在命令输出到达 LLM 上下文之前进行过滤与压缩。仓库对过滤器开发强制推行 Red-Green-Refactor并在传统三步之外额外增加两个 RTK 特有的步骤。文档定义的完整闭环如下1. RED — 编写基于真实 fixture 的失败测试 2. GREEN — 实现最小代码使测试通过 3. REFACTOR — 清理代码确认测试仍然通过 4. SAVINGS — 验证 token 缩减率 ≥60% 5. SNAPSHOT — 用 insta 锁定输出格式其中第 4 步是 rtk 区别于一般 TDD 的关键过滤器的存在意义就是压缩 token因此节省率本身必须是一条可执行的测试断言而不是上线后人工评估的指标。第 5 步则用快照测试snapshot test锁定输出格式防止后续重构悄悄改变 LLM 看到的输出结构。这与仓库贡献规范相互印证。CODING_PRACTICES.md 明确要求Write the test first. We follow Red-Green-Refactor并指出快照测试加 token 节省断言对大多数过滤器来说就足够了。第 1 步真实 Fixture 优先Real Fixture First文档第一条铁律绝不编写合成synthetic测试数据必须捕获真实命令输出。其给出的捕获方式是直接重定向真实命令的原始输出到 fixture 文件# 从真实命令捕获真实输出 git log -20 tests/fixtures/git_log_raw.txt cargo test 21 tests/fixtures/cargo_test_raw.txt cargo clippy 21 tests/fixtures/cargo_clippy_raw.txt gh pr view 42 tests/fixtures/gh_pr_view_raw.txt # 对带 ANSI 转义码的命令——按原样捕获 script -q /dev/null cargo test 21 tests/fixtures/cargo_test_ansi_raw.txtFixture 命名规范固定为tests/fixtures/command_raw.txt其中script -q /dev/null技巧用于让 TTY 感知型命令保留 ANSI 颜色码从而覆盖带转义序列的解析路径。仓库中的tests/fixtures/目录完整体现了这一约定maven 系列mvn_clean_raw.txt、mvn_test_multifail_slice_raw.txt、甚至 20MB 级mvn_install_full_raw.txt.gz、gradle 系列gradlew_build_raw.txt、gradlew_test_failed_raw.txt、sbt 系列tests/fixtures/sbt/ 下的sbt_test_munit_fail.txt等、ctest 系列ctest_parallel_output_on_failure_raw.txt、ctest_spoofed_framing_raw.txt以及 JSON 类命令的glab_mr_list_raw.json、golangci_v2_json.txt、oc_pods.json。可见该约定已从单测规范演化为整个仓库的 fixture 资产命名标准。第 2 步编写失败测试Red文档给出了一套完整的测试模板包含三类必须覆盖的测试。以下代码完整继承自 SKILL.md 原文以占位命令mycmd为例实际开发时替换为具体命令名#[cfg(test)] mod tests { use super::*; use insta::assert_snapshot; fn count_tokens(s: str) - usize { s.split_whitespace().count() } // Test 1: 输出格式快照 #[test] fn test_filter_output_format() { let input include_str!(../tests/fixtures/mycmd_raw.txt); let output filter_mycmd(input).expect(filter should not fail); assert_snapshot!(output); } // Test 2: Token 节省率 ≥60% #[test] fn test_token_savings() { let input include_str!(../tests/fixtures/mycmd_raw.txt); let output filter_mycmd(input).expect(filter should not fail); let input_tokens count_tokens(input); let output_tokens count_tokens(output); let savings 100.0 * (1.0 - output_tokens as f64 / input_tokens as f64); assert!( savings 60.0, Expected ≥60% token savings, got {:.1}% ({} → {} tokens), savings, input_tokens, output_tokens ); } // Test 3: 边界情况 #[test] fn test_empty_input() { let result filter_mycmd(); assert!(result.is_ok()); // 空输入 空输出或原样透传绝不能 panic } #[test] fn test_malformed_input() { let result filter_mycmd(not valid command output\nrandom text\n); // 不允许 panic——要么尽力过滤要么原样返回输入 assert!(result.is_ok()); } }三类测试的分工很清晰格式快照测试test_filter_output_format用include_str!在编译期内联真实 fixture对过滤结果做快照断言锁定输出的精确文本形态。节省率测试test_token_savings以空白分词split_whitespace近似 token 计数要求压缩后节省率不低于 60%失败信息中同时输出节省率百分比与原始/过滤后的 token 数便于直接定位回归。边界测试test_empty_input/test_malformed_input过滤器运行在代理层任何 panic 都会导致整条命令链对用户不可用因此空输入和畸形输入必须返回Ok这是可用性的硬性要求。执行cargo test后应当失败filter_mycmd尚不存在即完成 RED 阶段。该模式在仓库中有真实落地的例子glab_cmd.rs 中的test_mr_list_token_savings测试与文档模板几乎逐行一致——include_str!(../../../tests/fixtures/glab_mr_list_raw.json)内联真实 fixture计算savings并以assert!(savings 60.0, MR list: expected 60% savings, got {:.1}% ...)收口断言消息甚至把节省率、输入 token 数、输出 token 数一起打印出来与文档规定的失败信息格式完全吻合。第 3 步最小实现GreenRED 之后实现最小可通过代码。文档给出的参考实现展示了 rtk 过滤器实现的三个标准要素anyhow::Result返回类型、静态LazyLockRegex、以及逐行过滤// src/mycmd_cmd.rs use anyhow::{Context, Result}; use regex::Regex; use std::sync::LazyLock; static ERROR_RE: LazyLockRegex LazyLock::new(|| Regex::new(r^error).unwrap()); pub fn filter_mycmd(input: str) - ResultString { if input.is_empty() { return Ok(String::new()); } let filtered: Vecstr input.lines() .filter(|line| ERROR_RE.is_match(line)) .collect(); Ok(filtered.join(\n)) }关键设计点与仓库实际情况的对应关系LazyLockRegex静态变量正则只编译一次避免每次调用Regex::new。这一要求在仓库中被大规模遵守例如 binlog.rs 中集中声明了ISSUE_RE、BUILD_SUMMARY_RE、ERROR_COUNT_RE、DURATION_RE、TEST_RESULT_RE等一批static ...: LazyLockRegex常量psql_cmd.rs 同样以SEPARATOR、ROW_COUNT、RECORD_HEADER等静态正则组织解析逻辑。ResultString返回类型过滤器可以表达解析失败这一语义供上层run()决定是否回退透传见第 5 步。需要说明的是从源码结构看仓库当前并非所有过滤器都遵循此签名——部分早期过滤器如 prettier_cmd.rs 的filter_prettier_output、mypy_cmd.rs 的filter_mypy_output返回String而新过滤器按本工作流应返回ResultString。依赖齐备Cargo.toml 中regex 1、anyhow 1.0均已声明模板代码可直接编译。编译期 lint 约束Cargo.toml 的[lints.rust]段设置了unsafe_code deny和warnings deny意味着实现中任何未使用变量、unwrap隐患在 release 路径上都会被拒绝编译最小实现也必须在零警告下通过。执行cargo test测试转绿完成 GREEN 阶段。第 4 步快照确认Accept Snapshotinsta 快照测试的工作流为首跑生成 → 人工审查 → 接受落盘# 首次运行创建快照 cargo test test_filter_output_format # 审查捕获到的内容 cargo insta review # 按 a 接受 # 快照保存到 src/snapshots/mycmd_cmd__tests__test_filter_output_format.snap快照文件按模块__测试函数.snap命名约定落在源码同级的src/snapshots/目录使输出格式变更变成一次显式的、需要cargo insta review人工确认的版本化操作——这正是 SNAPSHOT 步骤的价值LLM 对输出结构的稳定性很敏感任何格式漂移都应当被当作 API 变更来对待。需要如实指出的是截至当前仓库状态Cargo.lock 中尚未出现insta依赖仓库内也未检索到.snap文件现存测试主要依赖include_str!fixture 加直接assert!断言如assert!(output.contains(Merge Requests))来锁定关键格式。因此 insta 快照属于该工作流规定的标准环节新过滤器接入时应先为 crate 添加insta开发依赖再执行上述流程对已存在的过滤器则可用等价的 contains/精确相等断言承担格式锁定职责。第 5 步接入 main.rs集成过滤器与测试就绪后接入 CLI 入口。SKILL.md 给出的接线模式包含两部分Commands 枚举注册src/main.rs// src/main.rs mod mycmd_cmd; #[derive(Subcommand)] pub enum Commands { // ... existing commands ... Mycmd(MycmdArgs), } // 在 match 中 Commands::Mycmd(args) mycmd_cmd::run(args),带回退的run()函数src/mycmd_cmd.rs// src/mycmd_cmd.rs — 添加 run() 函数 pub fn run(args: MycmdArgs) - Result() { let output execute_command(mycmd, args.to_vec()) .context(Failed to execute mycmd)?; let filtered filter_mycmd(output.stdout) .unwrap_or_else(|e| { eprintln!(rtk: filter warning: {}, e); output.stdout.clone() }); tracking::record(mycmd, output.stdout, filtered)?; print!({}, filtered); if !output.status.success() { std::process::exit(output.status.code().unwrap_or(1)); } Ok(()) }这段模板编码了代理工具最关键的一条原则过滤失败时绝不吞掉原始输出。unwrap_or_else分支在 stderr 打印警告并透传原始 stdout保证即使过滤器解析异常用户命令的退出码output.status与内容仍完整传回。与当前仓库实际结构的对照main.rs 中的Commands枚举确实以 clapSubcommand派生方式集中注册所有命令Ls、Tree、Read、Gh、Glab等数十个变体match 分发形如Commands::Gh { subcommand, args } gh_cmd::run(subcommand, args, cli.verbose, cli.ultra_compact)?参数结构上实际命令多采用{ subcommand, args: VecString }的透传形态而非独立 Args 结构体接线时以现有条目为参照即可。关于跟踪调用SKILL.md 中tracking::record(mycmd, stdout, filtered)是简化示意仓库中实际的持久化 API 是 tracking.rs 里的Tracker::record(original_cmd, rtk_cmd, input_tokens, output_tokens, exec_time_ms)它会把原始命令、rtk 命令、双向 token 数、节省比例与执行耗时写入 SQLitecommands表——这恰好与第 2 步测试中节省率 ≥60%的断言共用同一套 token 口径使测试断言与线上统计保持一致。第 6 步质量门禁Quality Gate三个检查必须全部通过且 clippy 零警告cargo fmt --all cargo clippy --all-targets cargo test结合 Cargo.toml 的warnings deny这道门禁实际上等价于格式合规 无 unsafe 无警告 全量测试含单元测试与集成测试全绿。仓库 CI 侧还有 scripts/test-all.sh 与 scripts/check-test-presence.sh 等脚本做补充校验本地提交前跑通上述一行命令即可对齐。Arrange-Act-Assert单元测试结构约定文档同时给出了行为级测试的三段式结构用于验证保留什么、丢弃什么的过滤语义#[test] fn test_filters_only_errors() { // Arrange let input info: starting build\nerror[E0001]: undefined\nwarning: unused\n; // Act let output filter_mycmd(input).expect(should succeed); // Assert assert!(output.contains(error[E0001]), Should keep error lines); assert!(!output.contains(info:), Should drop info lines); assert!(!output.contains(warning:), Should drop warning lines); }RTK 专属测试模式ANSI、回退与规模文档还规定了三个针对 CLI 代理场景的必测模式。ANSI 转义码剥离——输入按\x1b[32m...\x1b[0m原样构造对应第 1 步用script捕获的带色 fixture断言输出中不再含\x1b[且纯文本内容保留#[test] fn test_strips_ansi_codes() { let input \x1b[32mSuccess\x1b[0m\n\x1b[31merror: failed\x1b[0m\n; let output filter_mycmd(input).expect(should succeed); assert!(!output.contains(\x1b[), ANSI codes should be stripped); assert!(output.contains(error: failed), Content should be preserved); }回退行为——对完全意外的输入含 NUL、非法 UTF-8 字节过滤器只能返回Ok空输出或透传绝不 panic#[test] fn test_filter_handles_unexpected_format() { // 给它一个完全意外的输入 let input completely unexpected\x00binary\xff data; // 不允许 panic——返回 Ok()内容为空或原样透传 let result filter_mycmd(input); assert!(result.is_ok(), Filter must not panic on unexpected input); }多规模节省率——用程序化生成的 1000 行大输出验证节省率不随输入规模退化仍须达到 ≥60%#[test] fn test_savings_large_output() { // 1000 行 fixture → 仍必须达到 ≥60% let large_input: String (0..1000) .map(|i| format!(info: processing item {}\n, i)) .collect(); let output filter_mycmd(large_input).expect(should succeed); let savings 100.0 * (1.0 - count_tokens(output) as f64 / count_tokens(large_input) as f64); assert!(savings 60.0, Large output savings: {:.1}%, savings); }完成定义Definition of Done文档以一张清单作为过滤器交付的硬性门槛全部勾选项如下tests/fixtures/cmd_raw.txt—— 真实命令输出非合成数据filter_cmd()函数返回ResultString快照测试通过并经cargo insta review接受Token 节省率测试≥60% 已验证空输入测试不 panic畸形输入测试不 panic带回退模式的run()函数已注册进main.rs的Commands枚举cargo fmt --all cargo clippy --all-targets cargo test全绿反模式这些做法被明令禁止SKILL.md 末尾列出四类Never Do This与前述要求一一对应可作为 code review 时的检查项// ❌ 合成 fixture 数据 let input fake error: something went wrong; // 不是真实的 cargo 输出 // ❌ 缺少节省率测试 #[test] fn test_filter() { let output filter_mycmd(input); assert!(!output.is_empty()); // 没有任何节省率验证 } // ❌ 生产代码中 unwrap() let filtered filter_mycmd(input).unwrap(); // 生产环境 panic // ❌ 在过滤器函数内部新建正则 fn filter_mycmd(input: str) - ResultString { let re Regex::new(r^error).unwrap(); // 每次调用都重新编译 ... }四条禁令的本质分别是合成数据无法暴露真实输出中的版本漂移与多语言本地化问题缺失节省率断言等于放弃项目存在的根本指标unwrap违反代理层永不吞输出的回退原则函数内重复编译正则则被LazyLockRegex静态变量模式binlog.rs、psql_cmd.rs 中可见的成熟实践所取代。小结一套可复制的过滤器测试骨架将上述六步压缩成一张执行速查表阶段交付物验证命令REDtests/fixtures/cmd_raw.txt 失败测试cargo test应红GREENfilter_cmd() - ResultStringLazyLockRegex静态正则cargo test转绿SNAPSHOT已接受的.snap快照cargo insta review集成run()回退模式 Commands枚举注册cargo test全绿SAVINGS≥60% 节省率断言含 1000 行规模用例cargo test门禁fmt / clippy / test 零警告零失败cargo fmt --all cargo clippy --all-targets cargo test这套流程之所以适合 LLM token 优化类 CLI 工具在于它把压缩效果从主观评估变成了回归测试真实 fixture 保证输入的真实性节省率断言保证核心指标不回退快照锁定保证 LLM 看到的输出结构稳定回退测试保证代理层永不破坏用户命令。新过滤器开发者只需对照 SKILL.md 的模板与 tests/fixtures/ 中的既有资产即可完成接入。【免费下载链接】rtkCLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Rust binary, zero dependencies项目地址: https://gitcode.com/GitHub_Trending/rtk4/rtk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考