| name | tdd-rust |
| description | TDD workflow for RTK filter development. Red-Green-Refactor with Rust idioms. Real fixtures, token savings assertions, snapshot tests with insta. Auto-triggers on new filter implementation. |
| triggers | ["new filter","implement filter","add command","write tests for","test coverage","fix failing test"] |
| allowed-tools | ["Read","Write","Edit","Bash"] |
| effort | medium |
| tags | ["tdd","testing","rust","filters","snapshots","token-savings","rtk"] |
RTK TDD Workflow
Enforce Red-Green-Refactor for all RTK filter development.
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 insta
Step 1: Real Fixture First
Never write synthetic test data. Capture real command output:
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
script -q /dev/null cargo test 2>&1 > tests/fixtures/cargo_test_ansi_raw.txt
Fixture naming: tests/fixtures/<command>_raw.txt
Step 2: Write the Test (Red)
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
fn count_tokens(s: &str) -> usize {
s.split_whitespace().count()
}
#[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]
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]
fn test_empty_input() {
let result = filter_mycmd("");
assert!(result.is_ok());
}
#[test]
fn test_malformed_input() {
let result = filter_mycmd("not valid command output\nrandom text\n");
assert!(result.is_ok());
}
}
Run: cargo test → should fail (function doesn't exist yet).
Step 3: Minimum Implementation (Green)
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: cargo test → green.
Step 4: Accept Snapshot
cargo test test_filter_output_format
cargo insta review
Step 5: Wire to main.rs (Integration)
mod mycmd_cmd;
#[derive(Subcommand)]
pub enum Commands {
Mycmd(MycmdArgs),
}
Commands::Mycmd(args) => mycmd_cmd::run(args),
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
cargo fmt --all && cargo clippy --all-targets && cargo test
All 3 must pass. Zero clippy warnings.
Arrange-Act-Assert Pattern
#[test]
fn test_filters_only_errors() {
let input = "info: starting build\nerror[E0001]: undefined\nwarning: unused\n";
let output = filter_mycmd(input).expect("should succeed");
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
Test ANSI stripping
#[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
#[test]
fn test_filter_handles_unexpected_format() {
let input = "completely unexpected\x00binary\xff data";
let result = filter_mycmd(input);
assert!(result.is_ok(), "Filter must not panic on unexpected input");
}
Test savings at multiple sizes
#[test]
fn test_savings_large_output() {
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:
Never Do This
let input = "fake error: something went wrong";
#[test]
fn test_filter() {
let output = filter_mycmd(input);
assert!(!output.is_empty());
}
let filtered = filter_mycmd(input).unwrap();
fn filter_mycmd(input: &str) -> Result<String> {
let re = Regex::new(r"^error").unwrap();
...
}