Tdd rust
Monitor Claude Code sessions, costs, config, hooks, agents & MCP servers from a single Rust binary — TUI (9 tabs) + Web interface with live process tracking, budget alerts, and 30-day forecastingFrom the repository description
npx -y skills add FlorianBruniaux/ccboard --skill tdd-rustAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
5.2 KB, as published. Nobody here has run it
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