tdd-rust
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRTK TDD Workflow
RTK TDD 工作流
Enforce Red-Green-Refactor for all RTK filter development.
所有RTK过滤器开发均需遵循红-绿-重构流程。
The Loop
循环流程
1. RED — Write failing test with real fixture
2. GREEN — Implement minimum code to pass
3. REFACTOR — Clean up, verify still passing
4. SAVINGS — Verify ≥60% token reduction
5. SNAPSHOT — Lock output format with insta1. RED — 编写使用真实测试数据的失败测试用例
2. GREEN — 实现满足测试要求的最简代码
3. REFACTOR — 优化代码,验证测试仍通过
4. SAVINGS — 验证令牌减少率≥60%
5. SNAPSHOT — 使用insta锁定输出格式Step 1: Real Fixture First
步骤1:优先使用真实测试数据
Never write synthetic test data. Capture real command output:
bash
undefined绝不使用人工合成的测试数据。捕获真实命令的输出:
bash
undefinedCapture real output from the actual command
捕获实际命令的真实输出
git log -20 > tests/fixtures/git_log_raw.txt
cargo test 2>&1 > tests/fixtures/cargo_test_raw.txt
cargo clippy 2>&1 > tests/fixtures/cargo_clippy_raw.txt
gh pr view 42 > tests/fixtures/gh_pr_view_raw.txt
git log -20 > tests/fixtures/git_log_raw.txt
cargo test 2>&1 > tests/fixtures/cargo_test_raw.txt
cargo clippy 2>&1 > tests/fixtures/cargo_clippy_raw.txt
gh pr view 42 > tests/fixtures/gh_pr_view_raw.txt
For commands with ANSI codes — capture as-is
对于带ANSI代码的命令 — 原样捕获
script -q /dev/null cargo test 2>&1 > tests/fixtures/cargo_test_ansi_raw.txt
Fixture naming: `tests/fixtures/<command>_raw.txt`script -q /dev/null cargo test 2>&1 > tests/fixtures/cargo_test_ansi_raw.txt
测试数据命名规则:`tests/fixtures/<command>_raw.txt`Step 2: Write the Test (Red)
步骤2:编写测试用例(红阶段)
rust
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
fn count_tokens(s: &str) -> usize {
s.split_whitespace().count()
}
// Test 1: Output format (snapshot)
#[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 savings ≥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: Edge cases
#[test]
fn test_empty_input() {
let result = filter_mycmd("");
assert!(result.is_ok());
// Empty input = empty output OR passthrough, never panic
}
#[test]
fn test_malformed_input() {
let result = filter_mycmd("not valid command output\nrandom text\n");
// Must not panic — either filter best-effort or return input unchanged
assert!(result.is_ok());
}
}Run: → should fail (function doesn't exist yet).
cargo testrust
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
fn count_tokens(s: &str) -> usize {
s.split_whitespace().count()
}
// Test 1: Output format (snapshot)
#[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 savings ≥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: Edge cases
#[test]
fn test_empty_input() {
let result = filter_mycmd("");
assert!(result.is_ok());
// Empty input = empty output OR passthrough, never panic
}
#[test]
fn test_malformed_input() {
let result = filter_mycmd("not valid command output\nrandom text\n");
// Must not panic — either filter best-effort or return input unchanged
assert!(result.is_ok());
}
}运行: → 应失败(函数尚未存在)。
cargo testStep 3: Minimum Implementation (Green)
步骤3:最简实现(绿阶段)
rust
// src/mycmd_cmd.rs
use anyhow::{Context, Result};
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref ERROR_RE: Regex = Regex::new(r"^error").unwrap();
}
pub fn filter_mycmd(input: &str) -> Result<String> {
if input.is_empty() {
return Ok(String::new());
}
let filtered: Vec<&str> = input.lines()
.filter(|line| ERROR_RE.is_match(line))
.collect();
Ok(filtered.join("\n"))
}Run: → green.
cargo testrust
// src/mycmd_cmd.rs
use anyhow::{Context, Result};
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref ERROR_RE: Regex = Regex::new(r"^error").unwrap();
}
pub fn filter_mycmd(input: &str) -> Result<String> {
if input.is_empty() {
return Ok(String::new());
}
let filtered: Vec<&str> = input.lines()
.filter(|line| ERROR_RE.is_match(line))
.collect();
Ok(filtered.join("\n"))
}运行: → 测试通过(绿阶段)。
cargo testStep 4: Accept Snapshot
步骤4:接受快照
bash
undefinedbash
undefinedFirst run creates the snapshot
首次运行生成快照
cargo test test_filter_output_format
cargo test test_filter_output_format
Review what was captured
查看捕获的内容
cargo insta review
cargo insta review
Press 'a' to accept
按'a'键接受
Snapshot saved to src/snapshots/mycmd_cmd__tests__test_filter_output_format.snap
快照保存至 src/snapshots/mycmd_cmd__tests__test_filter_output_format.snap
undefinedundefinedStep 5: Wire to main.rs (Integration)
步骤5:接入main.rs(集成)
rust
// src/main.rs
mod mycmd_cmd;
#[derive(Subcommand)]
pub enum Commands {
// ... existing commands ...
Mycmd(MycmdArgs),
}
// In match:
Commands::Mycmd(args) => mycmd_cmd::run(args),rust
// src/mycmd_cmd.rs — add run() function
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(())
}rust
// src/main.rs
mod mycmd_cmd;
#[derive(Subcommand)]
pub enum Commands {
// ... existing commands ...
Mycmd(MycmdArgs),
}
// In match:
Commands::Mycmd(args) => mycmd_cmd::run(args),rust
// 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(())
}Step 6: Quality Gate
步骤6:质量门
bash
cargo fmt --all && cargo clippy --all-targets && cargo testAll 3 must pass. Zero clippy warnings.
bash
cargo fmt --all && cargo clippy --all-targets && cargo test三项必须全部通过,零clippy警告。
Arrange-Act-Assert Pattern
准备-执行-断言模式
rust
#[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");
}rust
#[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-Specific Test Patterns
RTK专属测试模式
Test ANSI stripping
测试ANSI代码剥离
rust
#[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");
}rust
#[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");
}Test fallback behavior
测试回退行为
rust
#[test]
fn test_filter_handles_unexpected_format() {
// Give it something completely unexpected
let input = "completely unexpected\x00binary\xff data";
// Should not panic — returns Ok() with either empty or passthrough
let result = filter_mycmd(input);
assert!(result.is_ok(), "Filter must not panic on unexpected input");
}rust
#[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");
}Test savings at multiple sizes
测试不同大小输出的令牌节省率
rust
#[test]
fn test_savings_large_output() {
// 1000-line fixture → must still hit ≥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);
}rust
#[test]
fn test_savings_large_output() {
// 1000行测试数据 → 仍需达到≥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);
}What "Done" Looks Like
完成标准
Checklist before moving on:
- — real command output
tests/fixtures/<cmd>_raw.txt - function returns
filter_<cmd>()Result<String> - Snapshot test passes and accepted via
cargo insta review - Token savings test: ≥60% verified
- Empty input test: no panic
- Malformed input test: no panic
- function with fallback pattern
run() - Registered in Commands enum
main.rs - — all green
cargo fmt --all && cargo clippy --all-targets && cargo test
继续下一步前需完成以下检查项:
- — 真实命令输出
tests/fixtures/<cmd>_raw.txt - 函数返回
filter_<cmd>()Result<String> - 快照测试通过并通过接受
cargo insta review - 令牌节省测试:验证≥60%的节省率
- 空输入测试:无panic
- 格式错误输入测试:无panic
- 带有回退模式的函数
run() - 在的Commands枚举中注册
main.rs - — 全部通过
cargo fmt --all && cargo clippy --all-targets && cargo test
Never Do This
禁止操作
rust
// ❌ Synthetic fixture data
let input = "fake error: something went wrong"; // Not real cargo output
// ❌ Missing savings test
#[test]
fn test_filter() {
let output = filter_mycmd(input);
assert!(!output.is_empty()); // No savings verification
}
// ❌ unwrap() in production code
let filtered = filter_mycmd(input).unwrap(); // Panic in prod
// ❌ Regex inside the filter function
fn filter_mycmd(input: &str) -> Result<String> {
let re = Regex::new(r"^error").unwrap(); // Recompiles every call
...
}rust
// ❌ 人工合成测试数据
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
// ❌ 过滤器函数内部定义Regex
fn filter_mycmd(input: &str) -> Result<String> {
let re = Regex::new(r"^error").unwrap(); // 每次调用都会重新编译
...
}