140 lines
7.1 KiB
Markdown
140 lines
7.1 KiB
Markdown
---
|
|
name: tdd-minimalism
|
|
user-invocable: true
|
|
description: "Use when: implementing code to pass tests, writing minimal implementations, TDD workflow. Enforces strict minimalism by analyzing tests first and generating only the absolute minimum code needed. Prevents overengineering and anticipatory code. Run this skill before implementation on any failing test."
|
|
---
|
|
|
|
# TDD Minimalism Guardrails
|
|
|
|
This skill enforces strict Test-Driven Development discipline by ensuring implementations contain **only** the code that tests require—nothing more.
|
|
|
|
## Principle
|
|
|
|
Generate **only the absolute minimum** needed to pass tests. Anticipatory code, infrastructure for future scenarios, defensive checks, comments explaining future work, or over-engineered data structures all violate TDD and should be deleted before writing implementation code.
|
|
|
|
## Pre-Implementation Workflow
|
|
|
|
Before writing implementation code, ALWAYS execute this checklist:
|
|
|
|
### 1. Analyze the Test
|
|
|
|
- Read the test completely
|
|
- Identify exactly what methods/functions the test **calls**
|
|
- Identify exactly what the test **asserts**
|
|
- Document nothing else matters for this test
|
|
|
|
### 2. Define Minimal State
|
|
|
|
Ask: "What data must persist across the test's method calls to satisfy the assertions?"
|
|
|
|
- Only that data gets stored as member variables
|
|
- Everything else is deleted before writing code
|
|
|
|
### 3. Implement Only Tested Paths
|
|
|
|
- Implement **only** the specific code paths the test exercises
|
|
- **No conditional logic** unless the test verifies multiple branches
|
|
- **No utility methods or helper classes**
|
|
- **No defensive code** (null checks, boundary conditions the test doesn't trigger)
|
|
- **No comments explaining future features**
|
|
- **No includes** you don't use
|
|
|
|
### 4. Question Every Line
|
|
|
|
For each piece of code, ask: "Does this test directly exercise this line?"
|
|
|
|
- **No** → Delete it immediately
|
|
- **Yes** → Keep it
|
|
|
|
## Anti-Patterns to Eliminate
|
|
|
|
| ❌ Anti-Pattern | Why It's Wrong | ✅ Corrected |
|
|
| ---------------------------------------------------------- | ---------------------------------------- | -------------------------- |
|
|
| Store data in a map/vector when test uses only 1 item | Implements multi-item support not tested | Use single member variable |
|
|
| Add `if` checks for untested scenarios | Defensive code the test doesn't exercise | Return value directly |
|
|
| Include `#include <map>` when using `int` | Unused dependency | Remove it |
|
|
| Define `lastChoice` member when `select()` doesn't read it | Dead code | Remove unused members |
|
|
| Comment explaining "future work" or "TODO" | Out of scope for current test | No implementation comments |
|
|
| Use compound assignment (`+=`, `-=`) when test calls method only once | Anticipates future multi-call scenarios not covered by any test | Use simple assignment (`=`) instead |
|
|
| Create utility methods not called in test | Anticipatory code | Delete before commit |
|
|
|
|
## Red-Green-Refactor Phases
|
|
|
|
Each phase **must** include running the test suite to confirm the expected outcome. The test command is `./run_tests.sh` (or the project's equivalent).
|
|
|
|
### Red Phase (Writing Tests)
|
|
|
|
1. Write a test describing one specific behavior
|
|
|
|
2. **Run the test suite** (`./run_tests.sh`) — confirm the test **fails** (either fails to compile or fails at runtime on an assertion).
|
|
- **If the test passes**: The behavior is already covered by existing code. **STOP. Abort and discuss with the user.** Options include: the test is redundant (delete it), the test name/assertions are wrong, or the requirement changed. Do NOT proceed without user input.
|
|
- **If the test fails to compile**: Write the absolute minimum stubs (empty classes, empty method signatures) needed to make the code compile, then **run the test suite again** — confirm the test now **fails at runtime** on the assertion (`EXPECT_*`/`ASSERT_*`), not just on compilation. This validates the assertion logic is correct before implementing.
|
|
- **If the test fails at runtime**: You may proceed to the Green phase.
|
|
|
|
3. Keep scope narrow (single scenario, not edge cases)
|
|
4. Test only what's currently required by the story
|
|
|
|
### Green Phase (Implementation)
|
|
|
|
1. Implement **only** what the test directly calls
|
|
2. Use the simplest possible data types
|
|
3. Use the shortest possible logic
|
|
4. **Run the test suite** (`./run_tests.sh`) — confirm it **passes**
|
|
5. Reference this skill's anti-patterns table and eliminate violations before committing
|
|
|
|
6. **Present the implementation to the user for review** before committing or proceeding to the next test.
|
|
- Show the final state of all changed files (diff or summary)
|
|
- Ask explicitly: "Please review and approve before I proceed"
|
|
- Do NOT move to the next test, commit, or merge without explicit user approval
|
|
- If the user requests changes, make them and re-run the test suite before asking for review again
|
|
|
|
### Refactor Phase
|
|
|
|
1. Improve code clarity (rename variables, format nicely)
|
|
2. **Run the test suite** (`./run_tests.sh`) — confirm tests still **pass**
|
|
3. **Never** add functionality without a failing test first
|
|
4. **Never** add infrastructure for "future" requirements
|
|
|
|
## Post-Implementation Verification
|
|
|
|
After implementing and tests pass, run this verification:
|
|
|
|
- [ ] Can I remove any member variables? (Are they all accessed in at least one method?)
|
|
- [ ] Can I remove any `#include` directives? (Do I use everything I include?)
|
|
- [ ] Can I remove any conditional branches? (Does the test exercise all paths?)
|
|
- [ ] Can I remove any comments? (Are they implementation comments, not documentation?)
|
|
- [ ] Can I remove any method parameters? (Are they all used in the method?)
|
|
- [ ] Can I replace any compound assignment (`+=`, `-=`, etc.) with simple assignment (`=`) without changing test outcomes?
|
|
|
|
If you answer **yes** to any question, make those deletions immediately. Do not proceed to the next test/feature without eliminating unnecessary code.
|
|
|
|
## Example: The Vending Machine
|
|
|
|
**Test** (what it calls and asserts):
|
|
|
|
```cpp
|
|
machine.addChoice(Choice::Cola, 0); // Calls this
|
|
machine.assignProduct(Choice::Cola, Brand::Coke); // Calls this
|
|
EXPECT_EQ(machine.select(Choice::Cola), Brand::Coke); // Asserts this
|
|
```
|
|
|
|
**Minimal Implementation:**
|
|
|
|
```cpp
|
|
// Header: ONE member variable (lastBrand), because select() returns it
|
|
Brand lastBrand;
|
|
|
|
// Implementation
|
|
void addChoice(Choice, int) {} // Body empty; test calls but doesn't verify behavior
|
|
void assignProduct(Choice choice, Brand brand) { lastBrand = brand; }
|
|
std::optional<Brand> select(Choice choice) { return lastBrand; } // No if; test only selects once
|
|
```
|
|
|
|
**Eliminated Code:**
|
|
|
|
- ❌ `#include <map>` — Not using maps
|
|
- ❌ `std::map<Choice, Brand> productMap` — Test uses only one assignment
|
|
- ❌ `if (it != productMap.end())` — Test doesn't verify "choice not found" scenario
|
|
- ❌ Comments explaining future work — Out of test scope
|
|
- ❌ `Choice lastChoice` — `select()` doesn't read it; never used
|