Test-Driven Development
Overview
One skill for all TDD workflows. Enforces test-first development using existing repository patterns. Three input modes handle different entry points — specs, task files, or ad-hoc descriptions — but the core cycle is always RED → GREEN → REFACTOR.
Input Modes
Detect the input type and follow the corresponding mode:
Mode A: From Spec (.spec.md )
Use when the input references a .spec.md file with Given/When/Then acceptance criteria.
-
Locate and parse the spec file — extract all Given/When/Then triples
-
Generate one test stub per criterion with todo!() bodies: /// Spec: <spec-file> — Criterion #<N> /// Given <given text> /// When <when text> /// Then <then text> #[test] fn <spec_name>criterion<N>_<slug>() { todo!("Implement: <then text>"); }
-
Verify stubs compile but fail: cargo test --no-run -p <crate>
-
Proceed to the TDD Cycle to make stubs pass
Programmatic support: ralph_core::preflight::{extract_acceptance_criteria, extract_criteria_from_file, extract_all_criteria} can parse criteria from spec files.
Mode B: From Task (.code-task.md )
Use when the input references a .code-task.md file or a specific implementation task.
-
Read the task and identify acceptance criteria or requirements
-
Discover patterns (see Pattern Discovery)
-
Design test scenarios covering normal operation, edge cases, and error conditions
-
Write failing tests for all requirements before any implementation
-
Proceed to the TDD Cycle
Mode C: From Description
Use for ad-hoc tasks without a spec or task file.
-
Clarify requirements from the description
-
Discover patterns (see Pattern Discovery)
-
Write failing tests targeting the described behavior
-
Proceed to the TDD Cycle
Pattern Discovery
Before writing tests, discover existing conventions:
rg --files -g "crates//tests/.rs" rg -n "#[cfg(test)]" crates/
Read 2-3 relevant test files near the target code. Mirror:
-
Test module layout, naming, and assertion style
-
Fixture helpers and test utilities
-
Use of tempfile , scenarios, or harnesses
TDD Cycle
- RED — Failing Tests
-
Write tests for the exact behavior required
-
Run tests to confirm failure for the right reason
-
If tests pass without implementation, the test is wrong
- GREEN — Minimal Implementation
-
Write the minimum code to make tests pass
-
No extra features or refactoring during this step
- REFACTOR — Clean Up
-
Improve implementation and tests while keeping tests green
-
Align with surrounding codebase conventions
-
Re-run tests after every change
Proptest Guidance
Use proptest only when ALL of:
-
Function is pure (no I/O, no time, no globals)
-
Deterministic output for given input
-
Non-trivial input space or edge cases
proptest! { #[test] fn round_trip(input in "[a-z0-9]{0,32}") { let encoded = encode(input.as_str()); let decoded = decode(&encoded).expect("should decode"); prop_assert_eq!(decoded, input); } }
Don't introduce proptest as a new dependency without strong justification.
Backpressure Integration
Include coverage evidence in completion events:
ralph emit "build.done" "tests: pass, lint: pass, typecheck: pass, audit: pass, coverage: pass (82%)"
Run cargo tarpaulin --out Html --output-dir coverage --skip-clean when feasible. If coverage cannot be run, state why and include targeted test evidence instead.
Test Location Rules
-
Spec maps to a single module → inline #[cfg(test)] tests
-
Spec spans multiple modules → integration test in crates/<crate>/tests/
-
CLI behavior → crates/ralph-cli/tests/
-
Follow existing patterns in the target crate
Anti-Patterns
-
Writing implementation before tests
-
Generating tests that pass without implementation
-
Copying tests from other crates without adapting to local patterns
-
Adding proptest when a simple example test suffices
-
Emitting completion events without coverage evidence