tdd-rust
TDD Workflow for Rust Projects
Enforce strict Test-Driven Development: Red → Green → Refactor
Workflow Steps
1. RED: Write failing test first
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_session_metadata() {
let path = Path::new("tests/fixtures/session.jsonl");
let metadata = extract_metadata(path).unwrap();
assert_eq!(metadata.message_count, 42);
assert!(metadata.first_timestamp < metadata.last_timestamp);
}
}
Run test to verify failure:
cargo test test_parse_session_metadata
# Expected: FAILED (function doesn't exist yet)
2. GREEN: Implement minimal code to pass
pub fn extract_metadata(path: &Path) -> anyhow::Result<SessionMetadata> {
// Minimal implementation
Ok(SessionMetadata {
message_count: 42, // Hardcoded for first pass
first_timestamp: Utc::now(),
last_timestamp: Utc::now(),
// ...
})
}
Run test again:
cargo test test_parse_session_metadata
# Expected: PASSED
3. REFACTOR: Improve implementation
pub fn extract_metadata(path: &Path) -> anyhow::Result<SessionMetadata> {
let file = File::open(path).context("Failed to open session file")?;
let reader = BufReader::new(file);
let mut first_line: Option<SessionLine> = None;
let mut last_line: Option<SessionLine> = None;
let mut count = 0;
for line in reader.lines() {
let line = line?;
if let Ok(parsed) = serde_json::from_str::<SessionLine>(&line) {
if first_line.is_none() {
first_line = Some(parsed.clone());
}
last_line = Some(parsed);
count += 1;
}
}
Ok(SessionMetadata {
message_count: count,
first_timestamp: first_line.map(|l| l.timestamp).unwrap_or_else(Utc::now),
last_timestamp: last_line.map(|l| l.timestamp).unwrap_or_else(Utc::now),
// ...
})
}
Final verification:
cargo test test_parse_session_metadata
cargo clippy --all-targets
Test Organization
Unit Tests (embedded in module)
// src/parsers/session_index.rs
pub fn extract_metadata(path: &Path) -> anyhow::Result<SessionMetadata> {
// Implementation
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_metadata_valid_file() {
// Test happy path
}
#[test]
fn test_extract_metadata_malformed_lines() {
// Test error handling
}
}
Integration Tests (tests/ directory)
// tests/integration_test.rs
use ccboard_core::store::DataStore;
#[tokio::test]
async fn test_initial_load() {
let store = DataStore::new();
let report = store.initial_load().await.unwrap();
assert!(report.stats_loaded);
}
Fixtures Management
Store sanitized test data in tests/fixtures/:
tests/
├─ fixtures/
│ ├─ stats-cache.json
│ ├─ session.jsonl
│ ├─ settings.json
│ └─ agent.md
└─ integration_test.rs
Load fixtures in tests:
#[test]
fn test_parse_stats() {
let fixture = include_str!("../tests/fixtures/stats-cache.json");
let stats: StatsCache = serde_json::from_str(fixture).unwrap();
assert!(stats.total_input_tokens > 0);
}
Async Tests
use tokio::test;
#[tokio::test]
async fn test_parallel_session_scan() {
let store = DataStore::new();
// Test concurrent operations
let tasks = vec![
store.reload_stats(),
store.update_session(path),
];
let results = futures::future::join_all(tasks).await;
// Assertions
}
Property-Based Testing (Optional)
# Cargo.toml
[dev-dependencies]
proptest = "1"
use proptest::prelude::*;
proptest! {
#[test]
fn test_session_parse_never_panics(s in "\\PC*") {
// Should handle any input without panicking
let _ = serde_json::from_str::<SessionLine>(&s);
}
}
Pre-Commit Checklist
Before EVERY commit:
cargo fmt --all # Format code
cargo clippy --all-targets # Lint
cargo test --all # All tests pass
Command Shortcuts
# Run specific test
cargo test test_name
# Run tests with output
cargo test -- --nocapture
# Run tests in module
cargo test parsers::
# Watch mode
cargo watch -x test
# Coverage (requires cargo-tarpaulin)
cargo tarpaulin --out Html
Anti-Patterns
❌ DON'T skip the RED phase (test must fail first)
❌ DON'T write implementation before test
❌ DON'T use .unwrap() in tests - use .expect("meaningful message")
❌ DON'T commit without running tests
✅ DO write test first, watch it fail ✅ DO implement minimal code to pass ✅ DO refactor after green ✅ DO use descriptive test names ✅ DO test both success and error paths
More from florianbruniaux/ccboard
ship
Build, commit, push & version bump workflow - automates the complete release cycle
1performance
Optimize web performance for faster loading and better user experience. Use when asked to "speed up my site", "optimize performance", "reduce load time", "fix slow loading", "improve page speed", or "performance audit".
1code-simplifier
|
1design-patterns
Analyze codebase for GoF design patterns - detection, suggestions, evaluation with stack-aware adaptations
1ccboard-remember
Store a fix, pattern, or context note in Brain knowledge base (~/.ccboard/insights.db)
1security-guardian
Expert en sécurité applicative pour détecter les vulnérabilités, auditer le code, et guider les bonnes pratiques de sécurité. OWASP Top 10, authentification, autorisation, cryptographie, gestion de secrets. Utiliser pour audits sécurité, reviews de code sensible, conception de features sécurisées, ou résolution de failles.
1