From forge-dev
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 claudepluginhub n4m3z/forge-devThis skill uses the workspace's default tool permissions.
Foundational engineering principle for all Forge ecosystem code. Write the test first, then make it pass, then refine.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Share bugs, ideas, or general feedback.
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 |