{"skill":{"slug":"tdd-ecc","displayName":"Test Driven Development","summary":"Test-driven development with red-green-refactor loop and de-sloppify pattern. Use when user wants to build features or fix bugs using TDD, mentions \"red-gree...","description":"---\nname: tdd\ndescription: Test-driven development with red-green-refactor loop and de-sloppify pattern. Use when user wants to build features or fix bugs using TDD, mentions \"red-green-refactor\", wants integration tests, or asks for test-first development. Includes cleanup pass for removing test/code slop.\n---\n\n# Test-Driven Development\n\n## Philosophy\n\n**Core principle**: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.\n\n**Good tests** are integration-style: they exercise real code paths through public APIs. They describe _what_ the system does, not _how_ it does it. A good test reads like a specification - \"user can checkout with valid cart\" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.\n\n**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.\n\nSee [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.\n\n## Anti-Pattern: Horizontal Slices\n\n**DO NOT write all tests first, then all implementation.** This is \"horizontal slicing\" - treating RED as \"write all tests\" and GREEN as \"write all code.\"\n\nThis produces **crap tests**:\n\n- Tests written in bulk test _imagined_ behavior, not _actual_ behavior\n- You end up testing the _shape_ of things (data structures, function signatures) rather than user-facing behavior\n- Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine\n- You outrun your headlights, committing to test structure before understanding the implementation\n\n**Correct approach**: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle. Because you just wrote the code, you know exactly what behavior matters and how to verify it.\n\n```\nWRONG (horizontal):\n  RED:   test1, test2, test3, test4, test5\n  GREEN: impl1, impl2, impl3, impl4, impl5\n\nRIGHT (vertical):\n  RED→GREEN: test1→impl1\n  RED→GREEN: test2→impl2\n  RED→GREEN: test3→impl3\n  ...\n```\n\n## Workflow\n\n### 1. Planning\n\nBefore writing any code:\n\n- [ ] Confirm with user what interface changes are needed\n- [ ] Confirm with user which behaviors to test (prioritize)\n- [ ] Identify opportunities for [deep modules](deep-modules.md) (small interface, deep implementation)\n- [ ] Design interfaces for [testability](interface-design.md)\n- [ ] List the behaviors to test (not implementation steps)\n- [ ] Get user approval on the plan\n\nAsk: \"What should the public interface look like? Which behaviors are most important to test?\"\n\n**You can't test everything.** Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case.\n\n### 2. Tracer Bullet\n\nWrite ONE test that confirms ONE thing about the system:\n\n```\nRED:   Write test for first behavior → test fails\nGREEN: Write minimal code to pass → test passes\n```\n\nThis is your tracer bullet - proves the path works end-to-end.\n\n### 3. Incremental Loop\n\nFor each remaining behavior:\n\n```\nRED:   Write next test → fails\nGREEN: Minimal code to pass → passes\n```\n\nRules:\n\n- One test at a time\n- Only enough code to pass current test\n- Don't anticipate future tests\n- Keep tests focused on observable behavior\n\n### 4. Refactor\n\nAfter all tests pass, look for [refactor candidates](refactoring.md):\n\n- [ ] Extract duplication\n- [ ] Deepen modules (move complexity behind simple interfaces)\n- [ ] Apply SOLID principles where natural\n- [ ] Consider what new code reveals about existing code\n- [ ] Run tests after each refactor step\n\n**Never refactor while RED.** Get to GREEN first.\n\n## Checklist Per Cycle\n\n```\n---\n\n## The De-Sloppify Pattern\n\n**An add-on pattern for TDD workflows.** Add a dedicated cleanup/refactor step after each implementation phase.\n\n### The Problem\n\nWhen you implement with TDD, LLMs take \"write tests\" too literally:\n- Tests that verify TypeScript's type system works (testing `typeof x === 'string'`)\n- Overly defensive runtime checks for things the type system already guarantees\n- Tests for framework behavior rather than business logic\n- Excessive error handling that obscures the actual code\n\n### Why Not Negative Instructions?\n\nAdding \"don't test type systems\" or \"don't add unnecessary checks\" to the implementer prompt has downstream effects:\n- The model becomes hesitant about ALL testing\n- It skips legitimate edge case tests\n- Quality degrades unpredictably\n\n### The Solution: Separate Pass\n\nInstead of constraining the implementer, let it be thorough. Then add a focused cleanup agent:\n\n```bash\n# Step 1: Implement (let it be thorough)\nclaude -p \"Implement the feature with full TDD. Be thorough with tests.\"\n\n# Step 2: De-sloppify (separate context, focused cleanup)\nclaude -p \"Review all changes in the working tree. Remove:\n- Tests that verify language/framework behavior rather than business logic\n- Redundant type checks that the type system already enforces\n- Over-defensive error handling for impossible states\n- Console.log statements\n- Commented-out code\n\nKeep all business logic tests. Run the test suite after cleanup to ensure nothing breaks.\"\n```\n\n### In a Loop Context\n\n```bash\nfor feature in \"${features[@]}\"; do\n  # Implement\n  claude -p \"Implement $feature with TDD.\"\n\n  # De-sloppify\n  claude -p \"Cleanup pass: review changes, remove test/code slop, run tests.\"\n\n  # Verify\n  claude -p \"Run build + lint + tests. Fix any failures.\"\n\n  # Commit\n  claude -p \"Commit with message: feat: add $feature\"\ndone\n```\n\n### Key Insight\n\n> Rather than adding negative instructions which have downstream quality effects, add a separate de-sloppify pass. Two focused agents outperform one constrained agent.\n\n### De-Sloppify Checklist\n\n```markdown\n## Cleanup Pass Checklist\n\n### Tests to Remove\n- [ ] Tests verifying language features (TypeScript types, JS prototypes)\n- [ ] Tests verifying framework behavior (React rendering, Next.js routing)\n- [ ] Tests for impossible states (already prevented by type system)\n- [ ] Duplicate test coverage (same scenario tested multiple ways)\n\n### Code to Remove\n- [ ] Redundant type guards after TypeScript checks\n- [ ] Unnecessary runtime validations\n- [ ] Console.log statements\n- [ ] Commented-out code\n- [ ] Dead code (unused functions/imports)\n\n### What to Keep\n- [ ] Business logic tests\n- [ ] Integration tests\n- [ ] Edge case handling for real scenarios\n- [ ] Security validations\n```\n\n---\n\n*Two focused agents outperform one constrained agent.*\n","topics":["Test Driven Development"],"tags":{"latest":"2.0.0","quality":"2.0.0","tdd":"2.0.0","testing":"2.0.0","workflow":"2.0.0"},"stats":{"comments":0,"downloads":708,"installsAllTime":26,"installsCurrent":2,"stars":0,"versions":1},"createdAt":1772937027238,"updatedAt":1778491773474},"latestVersion":{"version":"2.0.0","createdAt":1772937027238,"changelog":"tdd-ecc 2.0.0\n\n- Overhauled documentation with a detailed TDD philosophy, emphasizing public interface testing and integration-style tests.\n- Added clear anti-pattern examples, warning against \"horizontal slicing\" and bulk test writing.\n- Outlined a step-by-step TDD workflow, from planning to refactoring, including user confirmation and prioritization.\n- Introduced the \"de-sloppify\" pattern: a structured cleanup pass to remove superfluous or low-value tests and code after implementation.\n- Provided checklists and examples for the cleanup process, focusing on keeping tests relevant to business logic and removing framework/language redundancy.","license":null},"metadata":null,"owner":{"handle":"huamu668","userId":"s17e1xpgre78th3jf9p61ztyq5884zgm","displayName":"huamu668","image":"https://avatars.githubusercontent.com/u/202473531?v=4"},"moderation":null}