TDD Interactive Guide - Detroit School
SkillDev toolsInteractive guide for Detroit School TDD. Use whenever the user asks to write or modify code - new feature, bug fix, debug, refactoring, modification. Activates a RED-GREEN-REFACTOR workflow with validation at each step.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the TDD Interactive Guide - Detroit School skill
What this skill tells your AI
The instructions your AI receives, as published by dgouron/review-flow in .claude/skills/tdd/SKILL.md and read by ahel’s review.
Persona
Read .claude/roles/senior-dev.md — adopt this profile and follow all its rules.
Detroit School Philosophy
State-based testing: We test the observable result, not the internal interactions.
| Principle | Explanation |
|---|---|
| Test state | Verify the final result, not how we got there |
| Inside-Out | Start from the domain, work outward |
| Minimal mocks | Only for external I/O (gateways, API, DB) |
| Robust tests | Resistant to internal refactoring |
When to mock:
- Gateways (API, database, file system)
- External services (email, notifications)
- Internal business logic
- Collaborations between domain objects
// Detroit: we test the final STATE of the result
it("should enqueue a review job", () => {
const queue = new ReviewQueue();
queue.enqueue({ mrId: "mr-42", platform: "gitlab" });
expect(queue.pending).toHaveLength(1);
expect(queue.peek()?.mrId).toBe("mr-42");
});
// London: we test interactions (avoid this in this project)
it("should call dispatcher.notify", () => {
const dispatcher = mock<JobDispatcher>();
queue.enqueue({ mrId: "mr-42", platform: "gitlab" });
expect(dispatcher.notify).toHaveBeenCalled();
});
ReviewFlow example (project-relevant test):
// src/tests/units/entities/reviewScore.test.ts
describe("ReviewScore", () => {
// Detroit: we test the STATE of the result
it("should create a valid review score", () => {
const score = createReviewScore(8);
expect(score.value).toBe(8);
expect(score.label).toBe("good");
});
// Validation before use
it("should reject a score below 0", () => {
const result = createReviewScore(-1);
expect(result).toBeNull(); // Final state
});
// Boundary validation
it("should reject a score above 10", () => {
const result = createReviewScore(11);
expect(result).toBeNull(); // Final state
});
});
TDD Manifesto
TDD follows these fundamental principles:
| Principle | Meaning |
|---|---|
| Baby steps | Small steps for fast and regular feedback |
| Continuous refactoring | We improve now, not "later" |
| Evolutionary design | We develop what is necessary and sufficient |
| Executable documentation | Tests ARE the living documentation |
| Minimalist code | Simple and functional > over-dimensioned |
Minimal Test Principle
"The simplest thing that could possibly work." — Kent Beck
"As the tests get more specific, the code gets more generic." — Robert C. Martin
Rules:
- One behavior per test
- From naive to complete: simple case first, edge cases after
- No anticipation: one cycle at a time
Typical progression:
Cycle 1: "should create a completion" (nominal case)
Cycle 2: "should have a rating" (property)
Cycle 3: "should reject rating below 1" (validation)
Cycle 4: "should reject rating above 5" (validation)
Anti-pattern:
// Test too broad
it("should validate a completion with rating 1-5 and ISO datetime", () => {
// tests rating min, max, date format, nominal case...
})
// One behavior per test
it("should create a completion", () => { ... })
it("should reject rating below 1", () => { ... })
Transformation Priority Premise (Robert C. Martin)
The Transformation Priority Premise guides the RED → GREEN transition. When writing minimal code to pass a test, we apply transformations — and these transformations have a natural priority order.
Always choose the highest transformation in the list (the simplest). Skipping steps often leads to suboptimal design.
Transformation list (simplest to most complex)
| Priority | Transformation | Description | Example |
|---|---|---|---|
| 1 | {} → nil | No code → return null/undefined | return undefined |
| 2 | nil → constant | Return a hardcoded value | return [] |
| 3 | constant → variable | Replace constant with parameter | return items instead of return [] |
| 4 | unconditional → conditional | Add a branch | if (age < 18) return false |
| 5 | scalar → collection | Go from single value to array | string → string[] |
| 6 | statement → recursion/iteration | Loop over the collection | items.map(transform) |
| 7 | value → mutated value | Transform the accumulated value | items.reduce(accumulate) |
When to apply
The TPP is a guide, not an absolute rule. It is particularly useful when:
- Going GREEN seems to require "a lot of code at once" → sign that we're skipping transformations
- We hesitate between implementations → choose the highest transformation
- Working on algorithmic logic (parsers, calculations, data transformations, complex validations)
Less relevant for simple orchestration code (call a gateway and return the result).
Concrete example — a presenter formatting tags
// RED: test "should return empty list when no tags"
// GREEN via {} → constant:
return [];
// RED: test "should return formatted tag for single item"
// GREEN via constant → variable:
return [formatTag(tags[0])];
// RED: test "should return formatted tags for multiple items"
// GREEN via scalar → collection + iteration:
return tags.map(formatTag);
If we had jumped directly to map() on the first test, it works — but we would have guessed the design instead of discovering it through tests.
Warning signal
If during GREEN phase you end up writing more than 3-5 lines of code, you're probably skipping transformations. Options:
- Go back and add a simpler intermediate test
- Check which transformation you're applying in the list
Activation
This skill activates whenever the user asks to touch code:
- New features: "Implement...", "Add...", "Create..."
- Bug fixes: "Fix...", "Correct...", "Repair..."
- Debug: "Why does...", "It doesn't work..."
- Modifications: "Modify...", "Change...", "Update..."
- Refactoring: "Refactor...", "Improve...", "Clean up..."
Mandatory Workflow
At each cycle, follow these 3 phases with stop and user validation between each.
Phase RED
Goal: Write ONE failing test
Actions:
- Announce: "RED: I will test [specific behavior]"
- Identify the smallest possible test (baby step)
- Propose the test WITHOUT writing it
- Wait for validation
- Write the test after validation
- Run
yarn test:runto confirm failure - Ask: "The test fails as expected. Move to GREEN?"
Template:
RED - Test Proposal
Behavior to test: [description]
File: [path]
Proposed test:
[test code - state-based, verifies the result]
This test verifies that [explanation of expected state].
Validate this test?
Phase GREEN
Goal: Make the test pass with MINIMAL code
Actions:
- Announce: "GREEN: I will make the test pass with minimum code"
- Propose the minimal implementation WITHOUT writing it
- Wait for validation
- Write the code after validation
- Run
yarn test:runto confirm success - Ask: "The test passes. Refactor or next cycle?"
Rules:
- MINIMAL code that makes the test pass
- No premature optimization
- Hardcoded values accepted if sufficient
Template:
GREEN - Implementation Proposal
To make the test pass, I propose:
[minimal code]
This is intentionally minimal because [explanation].
Validate this implementation?
Phase REFACTOR
Goal: Simplify without changing behavior
Principles:
- KISS: The simplest solution
- YAGNI: Remove what is not necessary
- DRY: Factor out only if there is real duplication
Actions:
- Announce: "REFACTOR: Analyzing simplification opportunities"
- Look for: dead code, premature abstractions, accidental complexity
- Propose refactorings one by one
- Wait for validation for each
- Run
yarn test:runafter each refactoring - Ask: "Refactor complete. Next RED cycle?"
Priority order: Remove > Simplify > Reorganize
Template:
REFACTOR - Analysis
Code smells detected:
- [smell 1]: [explanation]
Proposed refactoring:
[description of change]
Apply this refactoring?
Special Case: Debug / Bug Fix
- Understand: Expected behavior vs actual
- RED: Test that reproduces the bug (must fail)
- GREEN: Fix so the test passes
- REFACTOR: Clean up if necessary
The test becomes a protective regression test.
Mandatory Checkpoints
Never move to the next phase without:
- Explicit user validation
- Tests executed and result confirmed
Specs and tickets: no predetermined code
TDD relies on progressive design discovery. Specs/tickets must NOT contain:
| Forbidden | Why |
|---|---|
| Test names | The test emerges from the need, not the other way around |
| File names | Architecture reveals itself through iteration |
| Method signatures | Design comes from the simplest code |
| Installation commands | Dependencies come when necessary |
Principle: We don't predict the final code. We proceed by baby steps, from the most naive implementation to the final case.
Ticket with "Tests to implement: should_create_user_with_email"
Ticket with "Files: src/services/user.service.ts"
Ticket with "Method: createUser(email: string): User"
Ticket with Gherkin criteria describing the expected BEHAVIOR
The RED-GREEN-REFACTOR cycle makes the design emerge. The ticket describes the WHAT (behavior), not the HOW (implementation).
Anti-patterns to Block
- Production code without a red test
- Multiple tests at once
- Implementing more than necessary in GREEN
- Refactoring without green tests
- Skipping a phase without validation
- Mocking internal business logic
- Predetermining code in specs/tickets
End of Cycle
Cycle Complete
Tests added: [number]
Behaviors covered: [list]
Next suggested behavior: [suggestion]
Continue?
Signals
- GitHub stars
- 43
- Forks
- 6
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
tdd-dgouron- Source
- github.com/dgouron/review-flow