ALL bug fixes and new features MUST follow Test-Driven Development.
- Confidence: A test that never failed might be testing the wrong thing
- Documentation: Tests show exactly what bug was fixed
- Regression Prevention: Proves the test will catch the bug if it returns
- Design: Writing tests first leads to better API design
Before writing ANY fix or feature code:
#[test]
fn test_bug_description() {
// Arrange: Set up the scenario that triggers the bug
let input = create_buggy_scenario();
// Act: Call the buggy function
let result = buggy_function(input);
// Assert: This SHOULD fail with current code
assert_eq!(result, expected_value, "Bug: description of what's wrong");
}CRITICAL: Run cargo test and VERIFY THE TEST FAILS
cargo test test_bug_description
# Expected output: ❌ FAILEDIf the test passes immediately, you're testing the wrong thing!
Now write the simplest code that makes the test pass:
pub fn buggy_function(input: Input) -> Output {
// The fix goes here
}Run tests again:
cargo test test_bug_description
# Expected output: ✅ okNow improve the code quality without changing behavior:
- Extract functions
- Improve names
- Add documentation
- Remove duplication
Run tests after each refactor to ensure nothing broke.
❌ WRONG APPROACH (What we did):
- Wrote
normalize_path()function - Wrote tests afterward
- Tests all passed immediately
⚠️ No confidence the tests catch the bug!
✅ CORRECT APPROACH (TDD):
// STEP 1: RED - Write failing test
#[test]
#[cfg(target_os = "windows")]
fn test_frontend_paths_get_normalized() {
let frontend_path = "C:/Users/test/ushadow";
let normalized = normalize_path(frontend_path);
assert!(!normalized.contains('/'),
"Frontend paths should have no forward slashes on Windows: {}",
normalized);
}
// Run: cargo test → ❌ compilation error: no function `normalize_path`// STEP 2: GREEN - Minimal implementation
pub fn normalize_path(path: &str) -> String {
#[cfg(target_os = "windows")]
{
path.replace('/', "\\")
}
#[cfg(not(target_os = "windows"))]
{
path.to_string()
}
}
// Run: cargo test → ✅ passes// STEP 3: REFACTOR - Add documentation, edge case tests
/// Normalize path separators to the platform standard
///
/// On Windows: Converts all forward slashes to backslashes
/// On Unix: Returns path unchanged
pub fn normalize_path(path: &str) -> String {
// ... same implementation
}
// Run: cargo test → ✅ still passesWhen an AI agent (Claude, GitHub Copilot, etc.) is fixing a bug or adding a feature:
- ALWAYS ask: "Can you show me a failing test first?"
- VERIFY the test fails before accepting the fix
- DOCUMENT the failure output in comments or commit messages
- NEVER write tests after the fix - this defeats the purpose
In PR reviews, check:
- Test was written before the fix (check git history)
- Commit shows test failing first
- Test specifically targets the bug/feature
- Test would catch regression if code was reverted
- Write test that reproduces the bug (should fail)
- Fix the bug (test should pass)
- Document the original failure in test comments
- Write test for simplest case (should fail)
- Implement minimal feature (test passes)
- Add tests for edge cases
- Refactor for clarity
- Ensure existing tests pass
- Refactor code
- Tests still pass (behavior unchanged)
We provide scripts/verify-tdd.sh as an optional tool to help verify TDD practices:
# Run manually before creating PR
./scripts/verify-tdd.sh
# Or add to pre-commit hook (optional, can be intrusive)
# .git/hooks/pre-commit
./scripts/verify-tdd.sh
# Or run in CI pipeline
# .github/workflows/test.yml
- run: ./scripts/verify-tdd.shThe script checks:
- All tests pass
- Changed source files have corresponding tests
- Warns if tests and source are in same commit (suggests tests weren't written first)
Note: This is an optional helper, not mandatory. Use it if it helps your workflow.
Consider adding a hook that checks for:
- Tests in the same commit as bug fixes
- Test files modified before source files (by timestamp)
- Run
cargo teston every commit - Fail builds if test coverage drops
- Require test-to-code ratio thresholds
Added to CLAUDE.md:
- All bug fixes MUST include failing test first
- Agent must show test failure before proposing fix
- Agent should verify fix by re-running tests
git commit -m "Fix Windows path bug + add tests"
Problem: Can't verify test catches the bug!
git commit -m "Add failing test for Windows path bug"
git commit -m "Fix Windows path separator normalization"
git commit -m "Add edge case tests for path normalization"
#[test]
fn test_addition() {
assert_eq!(2 + 2, 4); // This will always pass
}#[test]
fn test_windows_mixed_separators_bug() {
// This would fail before fix:
// Expected: "C:\\Users\\test\\file"
// Got: "C:\\Users\\test/file"
let result = normalize_path("C:/Users/test/file");
assert!(!result.contains('/'));
}- Test-Driven Development by Example - Kent Beck
- Growing Object-Oriented Software, Guided by Tests
- Rust Testing Guide: https://doc.rust-lang.org/book/ch11-00-testing.html
┌─────────────────────────────────────────────┐
│ TDD CHECKLIST │
├─────────────────────────────────────────────┤
│ □ Write test first │
│ □ Run test - verify it FAILS │
│ □ Write minimal code to pass │
│ □ Run test - verify it PASSES │
│ □ Refactor for clarity │
│ □ Run test - still PASSES │
│ □ Commit with descriptive message │
└─────────────────────────────────────────────┘