skills/TestDrivenDevelopment/SKILL.md
Test-driven development practices — Red-Green-Refactor cycle, test categories, coverage strategy, property-based testing. USE WHEN writing tests, designing testable APIs, or reviewing test coverage.
npx skillsauth add n4m3z/forge-dev TestDrivenDevelopmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Foundational engineering principle for all Forge ecosystem code. Write the test first, then make it pass, then refine.
This cycle produces code that is testable by design, not testable by accident.
Test individual functions and modules in isolation. These are the backbone:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_frontmatter_value_finds_simple_key() {
let content = "---\ntitle: My Note\nclaude.name: TestAgent\n---\nBody";
assert_eq!(
extract_frontmatter_value(content, "claude.name"),
Some("TestAgent".to_string())
);
}
#[test]
fn extract_frontmatter_value_handles_quoted_values() {
let content = "---\ndescription: \"A quoted value\"\n---\n";
assert_eq!(
extract_frontmatter_value(content, "description"),
Some("A quoted value".to_string())
);
}
#[test]
fn extract_frontmatter_value_returns_none_for_missing() {
let content = "---\ntitle: Note\n---\n";
assert_eq!(extract_frontmatter_value(content, "missing"), None);
}
}
Convention: Put unit tests in a tests.rs file alongside mod.rs, referenced by #[cfg(test)] mod tests; at the top of the module.
Test binary behavior end-to-end. Use std::process::Command:
#[test]
fn safe_read_strips_red_sections() {
let output = Command::new(env!("CARGO_BIN_EXE_safe-read"))
.arg("tests/fixtures/mixed-tlp.md")
.output()
.expect("failed to run safe-read");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("## Public Section"));
assert!(!stdout.contains("#tlp/red"));
assert!(!stdout.contains("secret content"));
}
Every function should have tests for boundary conditions:
#[test]
fn handles_empty_input() {
assert_eq!(extract_frontmatter_value("", "key"), None);
}
#[test]
fn handles_no_frontmatter() {
assert_eq!(extract_frontmatter_value("Just text", "key"), None);
}
#[test]
fn handles_empty_frontmatter() {
assert_eq!(extract_frontmatter_value("---\n---\nBody", "key"), None);
}
#[test]
fn handles_frontmatter_without_closing() {
assert_eq!(extract_frontmatter_value("---\nkey: val\n", "key"), None);
}
For functions with well-defined invariants, use proptest or quickcheck:
#[cfg(test)]
mod proptests {
use proptest::prelude::*;
proptest! {
#[test]
fn roundtrip_slugify(name in "[A-Z][a-zA-Z]{1,30}") {
let slug = slugify(&name);
// slug is always lowercase kebab-case
assert!(slug.chars().all(|c| c.is_ascii_lowercase() || c == '-'));
// slug is never empty
assert!(!slug.is_empty());
}
}
}
src/
├── frontmatter/
│ ├── mod.rs # pub fn extract_value(...) + #[cfg(test)] mod tests;
│ └── tests.rs # all unit tests
├── deploy/
│ ├── mod.rs # pub fn deploy_agent(...) + #[cfg(test)] mod tests;
│ └── tests.rs # all unit tests
lib/tests/
├── helpers.sh # Shared assertions (assert_eq, assert_contains, report)
├── test-module-structure.sh # Module layout validation
├── test-agent-frontmatter.sh # Agent file integrity
├── test-defaults-consistency.sh # Config consistency
├── test-skill-integrity.sh # Skill file validation
└── test-deploy-parity.sh # Deploy output matching source
Each shell test uses assert_eq, assert_contains, assert_file_exists from helpers and calls report to summarize PASS/FAIL.
Use dedicated fixture directories for test data:
#[test]
fn parses_agent_frontmatter() {
let content = include_str!("../../tests/fixtures/agent.md");
let fm = parse_frontmatter(content).expect("valid frontmatter");
assert_eq!(fm.name, "Developer");
assert_eq!(fm.model, "sonnet");
}
For generated fixtures, create them in test setup:
fn fixture_agent(name: &str) -> String {
format!(
"---\ntitle: {name}\nclaude.name: {name}\nclaude.model: sonnet\n\
claude.description: \"Test agent\"\nclaude.tools: Read, Grep\n---\n\nBody.\n"
)
}
| Component | Required Coverage | Method | |-----------|-------------------|--------| | Frontmatter parsing | 100% branch | Unit tests with valid/invalid/edge inputs | | Config loading | All fallback paths | Unit tests with missing/corrupt files | | Agent deployment | All providers | Integration tests per provider format | | Skill installation | Happy + error paths | Integration tests | | TLP redaction | 100% (security-critical) | Unit + property tests |
#[cfg] to skip)# All Rust tests in a workspace
cargo test --workspace
# Single test in a specific crate
cargo test --manifest-path <crate>/Cargo.toml -- test_name
# With output
cargo test -- --nocapture
| Thought | Reality | | -------------------------------------------- | ---------------------------------------------------------------- | | "Too simple to test" | Simple code breaks. Test takes 30 seconds. | | "I'll write tests after" | Tests passing immediately prove nothing. | | "Tests after achieve the same goals" | Tests-after verify what you built. Tests-first verify what's needed. | | "I already manually tested it" | Ad-hoc is not systematic. No record, can't re-run. | | "Deleting X hours of work is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. | | "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. | | "Need to explore first" | Fine. Throw away exploration, start with TDD. | | "Test is hard to write — design is unclear" | Listen to the test. Hard to test means hard to use. | | "TDD will slow me down" | TDD is faster than debugging. Pragmatic means test-first. | | "This is different because..." | It's not. Delete code. Start over with TDD. |
| Anti-Pattern | Why | Fix |
|-------------|-----|-----|
| Testing after implementation | Shapes code around existing structure, not behavior | Write test first |
| Testing implementation details | Breaks on refactor | Test public API behavior |
| Large integration tests only | Slow, hard to debug | Unit tests first, integration for wiring |
| Ignoring test failures | Normalizes broken code | Fix immediately or mark #[ignore] with reason |
| Copy-paste test cases | Maintenance burden | Use parameterized tests or fixtures |
| Mocking everything | Tests pass but nothing works | Mock at boundaries only |
tools
Server-rendered web dashboards and apps in Rust using axum + htmx + Askama + rust-embed. USE WHEN building a web dashboard, adding a web UI to a CLI tool, server-rendered HTML, htmx partials, Askama templates, axum routes, embedded static assets, localhost webserver.
tools
Architecture for security and health-check programs: standalone-runnable checks, severity ladder with UNKNOWN, key:value output contract, orchestrator dispatch, exit-code semantics. Language-agnostic; reference implementations in Bash, applies to Python and other languages. USE WHEN writing health checks or audit tools, designing check-script contracts, adding checks to tools like check-mac, reviewing health-check architecture, or porting a check tool between languages.
testing
Scan a dotfiles tree for secrets via gitleaks, aggregate findings by top-level directory and rule, then surgically filter flagged lines from shell-history files before importing into atuin. USE WHEN auditing dotfiles before pushing to a public repo, scanning rsynced dotfiles-private contents, importing legacy zsh or bash history into atuin, filtering credential leaks out of a shell history file, deciding which dotfile subdirectories can be made public.
development
Four-phase debugging methodology — root cause before fixes. USE WHEN encountering any bug, test failure, unexpected behavior, or build failure.