tdd · diff
git:20260313.0860d48 to git:20260325.3fb4c8a
174 added, 12 removed. Audit A to A.
---
name: tdd
description: >
Use when implementing any feature or fix outside code-forge workflow — enforces
- Red-Green-Refactor cycle with mandatory test-first discipline. For ad-hoc development,
- quick fixes, or any code change that is not tracked by code-forge:impl.
+ Red-Green-Refactor cycle with mandatory test-first discipline. Supports three modes:
+ (1) Standalone — ad-hoc TDD for quick changes, (2) Auto-Analysis — scans code to
+ design test cases then implements them, (3) Driven — reads a test-cases.md document
+ and implements each case via TDD.
---
# Code Forge — TDD
- Standalone Test-Driven Development enforcement for any code change.
+ Test-Driven Development enforcement for any code change, with built-in code analysis.
## When to Use
- Writing code outside of code-forge:impl workflow (ad-hoc changes, quick fixes)
+ - Adding tests to existing code that lacks coverage
+ - Implementing test cases from a spec-forge:test-cases document
- Any new feature, bug fix, or behavior change that needs test discipline
- - When you catch yourself about to write production code without a test
**Note:** code-forge:impl already enforces TDD internally. This skill is for work outside that workflow.
## Iron Law
**NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.**
No exceptions. Not for "simple" changes. Not for "obvious" fixes. Not when under time pressure.
- ## Workflow
+ ## Step 0: Determine Mode
+ Examine the arguments to determine the operating mode:
+
+ | Argument | Mode | Behavior |
+ |----------|------|----------|
+ | `@docs/.../test-cases.md` | **Driven Mode** | Read test cases document, implement each case via TDD |
+ | `@src/services/payment.ts` or specific code path | **Auto-Analysis Mode** | Analyze specified code, design cases, implement via TDD |
+ | Feature name or description (e.g., "add validation to user signup") | **Standalone Mode** | Classic TDD — write tests for the described change |
+ | Empty (no arguments) | **Auto-Analysis Mode** | Scan project for coverage gaps, design cases, implement |
+
+ ## Driven Mode — Implementing from Test Cases Document
+
+ When a `test-cases.md` file is provided (generated by `spec-forge:test-cases`):
+
+ ### D.1 Read and Parse
+
+ 1. Read the test-cases document
+ 2. Extract all test cases (TC-MODULE-NNN entries)
+ 3. Identify which are already implemented (check existing test files for matching test names/IDs)
+ 4. Filter to unimplemented cases
+ 5. Sort by priority: P0 first, then P1, then P2
+
+ ### D.2 Confirm Scope
+
+ Present to user:
+ - "{N} test cases found, {X} already implemented, {Y} remaining"
+ - "Implement: (A) all remaining, (B) P0 only, (C) P0 + P1, (D) specific modules?"
+
+ ### D.3 Implement Loop
+
+ For each test case in scope:
+
+ 1. **Read the case** — extract preconditions, steps, expected result, not-expected, test infra
+ 2. **Set up test infrastructure** — if Test Infra is "Real DB", configure TestContainers or test database; if "Mock external", set up mock for the specified third-party service; if "Temp dir", create temp directory; if "N/A", no special setup needed
+ 3. **RED** — Write a failing test that matches the case specification
+ - Test name should include TC ID: `test("TC-AUTH-001: create user with valid email returns 201", ...)`
+ - Preconditions become test setup (seed data, auth context, config)
+ - Steps become test actions
+ - Expected result becomes assertions
+ - "Not Expected" becomes negative assertions where applicable
+ 4. **VERIFY RED** — Run the test, confirm it fails correctly
+ 5. **GREEN** — Write minimal production code to make it pass (if the code already exists and passes, the case was already covered — note and move on)
+ 6. **VERIFY GREEN** — Run all tests, confirm clean pass
+ 7. **REFACTOR** — Clean up if needed
+ 8. **Report** — "TC-AUTH-001: DONE (test passes, implementation complete)"
+
+ ### D.4 Progress Tracking
+
+ After each case, display progress:
```
+ TDD Progress: {completed}/{total} ({percentage}%)
+ [x] TC-AUTH-001: Create user with valid email (P0) — DONE
+ [x] TC-AUTH-010: Create user with duplicate email rejected (P0) — DONE
+ [ ] TC-AUTH-011: Create user with invalid email format (P1) — next
+ [ ] TC-AUTH-030: Create user should NOT bypass email validation (P1)
+ ```
+
+ Ask: "Continue with next case, skip, or pause?"
+
+ ### D.5 Completion
+
+ After all cases are implemented:
+ - Run full test suite
+ - Report: total cases implemented, all tests passing, coverage change
+ - Suggest: "Run `/code-forge:verify` to confirm completion"
+
+ ## Auto-Analysis Mode — Scan and Test
+
+ When the user points to code or says "help me write tests" without a test-cases document:
+
+ ### A.0 Project Analysis
+
+ First, understand the project:
+
+ @../shared/project-analysis.md
+
+ Execute PA.1 (Project Profile), PA.2 (Architecture Analysis), PA.3 (Language-Specific Deep Scan), and PA.5 (Existing Test Assessment). This ensures:
+ - Test framework and runner are correctly identified
+ - Language-specific constructs are properly analyzed (Rust traits, Go interfaces, etc.)
+ - Architecture informs test strategy (unit vs. integration boundary decisions)
+
+ ### A.1 Analyze Target
+
+ Using the project context from A.0:
+
+ 1. **Identify target scope**:
+ - If specific file(s) given → deep-scan those files using the language strategy from PA.3
+ - If no target → use PA.5 results to find files without test coverage
+ 2. **Extract testable units** using the four-layer model from PA.3:
+ - **Interface**: public functions, methods, routes, components, commands (what CAN be tested)
+ - **Logic**: branch paths, error chains, state transitions (what SHOULD be tested)
+ - **Architecture**: which layer each unit belongs to (determines unit vs. integration test)
+ - **Relationships**: call graph, data flow (determines combination tests)
+ 3. **Scan existing tests** to identify what's already covered (from PA.5)
+ 4. **Identify dependencies** between units using PA.4 relationship mapping
+
+ ### A.2 Design Test Cases (Internal)
+
+ For each uncovered testable unit, design cases internally:
+
+ - **L1 (Happy Path)**: 1 case — basic correct behavior
+ - **L2 (Boundary/Error)**: 2 cases — edge case + error handling
+ - **L3 (Negative)**: 1 case — what should NOT happen
+
+ For units with interaction relationships:
+ - 1 combination case — units working together correctly
+
+ ### A.3 Confirm with User
+
+ Present the analysis:
+ ```
+ Found {N} testable units in {scope}:
+ {unit1} (function) — no tests → 4 cases planned
+ {unit2} (route) — partial coverage → 2 cases planned
+ {unit3} (function) — fully covered → skip
+
+ Total: {M} test cases to implement
+
+ Business logic I can't infer — anything to add?
+ (e.g., special rules, constraints, domain-specific edge cases)
+ ```
+
+ Wait for user confirmation or additions.
+
+ ### A.4 Implement via TDD
+
+ For each designed case, follow the standard TDD cycle:
+
+ 1. **RED** — Write failing test
+ 2. **VERIFY RED** — Confirm correct failure
+ 3. **GREEN** — Minimal code to pass
+ 4. **VERIFY GREEN** — All tests pass
+ 5. **REFACTOR** — Clean up
+ 6. **REPEAT** — Next case
+
+ Display progress after each case. Ask to continue/pause periodically.
+
+ ## Standalone Mode — Classic TDD
+
+ For ad-hoc changes where the user describes what to build or fix:
+
+ ### Workflow
+
+ ```
RED (write failing test) → VERIFY RED → GREEN (minimal code) → VERIFY GREEN → REFACTOR → REPEAT
```
- ## The Cycle
+ ### The Cycle
Complete each phase fully before moving to the next.
- ### 1. RED — Write a Failing Test
+ #### 1. RED — Write a Failing Test
- One minimal test showing the desired behavior
- Clear, descriptive test name
- Use real code, not mocks (unless unavoidable: external APIs, time-dependent behavior)
- One behavior per test
- ### 2. VERIFY RED — Watch It Fail (MANDATORY)
+ #### 2. VERIFY RED — Watch It Fail (MANDATORY)
Run the test. Confirm:
- It **fails** (not errors)
- The failure message describes the missing behavior
- It fails because the feature is missing, not because of typos or setup issues
If the test **passes**: you're testing existing behavior. Rewrite the test.
If the test **errors**: fix the error, re-run until it fails correctly.
- ### 3. GREEN — Write Minimal Code
+ #### 3. GREEN — Write Minimal Code
- Simplest code that makes the test pass
- No extra features, no "while I'm here" improvements
- No premature abstractions — three similar lines beats a premature helper
- ### 4. VERIFY GREEN — Watch It Pass (MANDATORY)
+ #### 4. VERIFY GREEN — Watch It Pass (MANDATORY)
Run the test. Confirm:
- The new test **passes**
- All other tests **still pass**
- Output is clean (no warnings, no errors)
If the new test **fails**: fix the code, not the test.
If other tests **fail**: fix them now, before proceeding.
- ### 5. REFACTOR — Clean Up (After Green Only)
+ #### 5. REFACTOR — Clean Up (After Green Only)
- Remove duplication, improve names, extract helpers
- Keep all tests green throughout
- Do NOT add new behavior during refactor
- ### 6. REPEAT
+ #### 6. REPEAT
Go back to Step 1 for the next behavior.
## Decision Rules
| If you're about to... | Instead... | Why |
|----------------------|-----------|-----|
| Write production code without a test | STOP — write the failing test first | Tests written after implementation pass immediately and prove nothing |
| Skip testing because the change is "simple" | Write the test — it will be quick if it's truly simple | Simple code has the sneakiest bugs (off-by-one, null edge cases) |
| Apply a quick fix without a regression test | Write the test, then fix | Untested fixes become permanent regressions |
| Continue with code that wasn't test-driven | Consider rewriting test-first | Sunk cost — untested code is a liability regardless of time spent |
+ ## External Dependency Rules
+
+ **Principle: test your own dependencies for real; only mock what you don't control.**
+
+ | Your Dependency | Approach |
+ |----------------|----------|
+ | Own database | Real DB (TestContainers, test instance, SQLite in-memory) |
+ | Own file system | Real temp directory |
+ | Own cache / message queue | Real (TestContainers, embedded) |
+ | External third-party API | Mock / stub acceptable |
+ | Non-deterministic input (time, random) | Inject controlled values |
+
+ - For projects **without** a database or external I/O: most tests are pure unit tests — no special infra needed
+ - For write operations: verify state after the operation (DB query / file check / store assertion)
+
## Example
```
Task: Add isPalindrome(str) function
1. RED — Write test:
test("isPalindrome returns true for 'racecar'", () => {
expect(isPalindrome("racecar")).toBe(true);
});
2. VERIFY RED — Run: npm test
✗ ReferenceError: isPalindrome is not defined ← fails correctly
3. GREEN — Minimal code:
function isPalindrome(str) {
return str === str.split("").reverse().join("");
}
4. VERIFY GREEN — Run: npm test
✓ isPalindrome returns true for 'racecar' ← passes
42 passed, 0 failed
5. REFACTOR — (no changes needed)
6. REPEAT — next test: edge case with empty string
```
**Test runner detection:** Check `package.json` scripts, `pytest.ini`, `Cargo.toml`, `go.mod`, or `Makefile` for the project's test command before starting the cycle. Use the same runner consistently.
## Verification Checklist
Before claiming work is complete:
- [ ] Every new function/method has at least one test
- [ ] Watched each test fail before implementing
- [ ] Each test failed for the expected reason (not errors)
- [ ] Wrote minimal code per test (no gold-plating)
- [ ] All tests pass with clean output
- [ ] Edge cases and error paths covered
- [ ] Mocks used only when unavoidable
+ - [ ] Database-touching tests use real database
## When Stuck
- Test too complicated to write → design is too complicated, simplify first
- Must mock everything → code is too coupled, extract interfaces
- Test setup is huge → extract test helpers or fixtures
+ - No test-cases document and unsure what to test → run `/spec-forge:test-cases` first to generate a structured case set