TDD Interactive Guide - Detroit School

SkillDev tools

Interactive 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.

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.

PrincipleExplanation
Test stateVerify the final result, not how we got there
Inside-OutStart from the domain, work outward
Minimal mocksOnly for external I/O (gateways, API, DB)
Robust testsResistant 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:

PrincipleMeaning
Baby stepsSmall steps for fast and regular feedback
Continuous refactoringWe improve now, not "later"
Evolutionary designWe develop what is necessary and sufficient
Executable documentationTests ARE the living documentation
Minimalist codeSimple 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:

  1. One behavior per test
  2. From naive to complete: simple case first, edge cases after
  3. 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)

PriorityTransformationDescriptionExample
1{} → nilNo code → return null/undefinedreturn undefined
2nil → constantReturn a hardcoded valuereturn []
3constant → variableReplace constant with parameterreturn items instead of return []
4unconditional → conditionalAdd a branchif (age < 18) return false
5scalar → collectionGo from single value to arraystring → string[]
6statement → recursion/iterationLoop over the collectionitems.map(transform)
7value → mutated valueTransform the accumulated valueitems.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:

  1. Go back and add a simpler intermediate test
  2. 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:

  1. Announce: "RED: I will test [specific behavior]"
  2. Identify the smallest possible test (baby step)
  3. Propose the test WITHOUT writing it
  4. Wait for validation
  5. Write the test after validation
  6. Run yarn test:run to confirm failure
  7. 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:

  1. Announce: "GREEN: I will make the test pass with minimum code"
  2. Propose the minimal implementation WITHOUT writing it
  3. Wait for validation
  4. Write the code after validation
  5. Run yarn test:run to confirm success
  6. 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:

  1. Announce: "REFACTOR: Analyzing simplification opportunities"
  2. Look for: dead code, premature abstractions, accidental complexity
  3. Propose refactorings one by one
  4. Wait for validation for each
  5. Run yarn test:run after each refactoring
  6. 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

  1. Understand: Expected behavior vs actual
  2. RED: Test that reproduces the bug (must fail)
  3. GREEN: Fix so the test passes
  4. 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:

ForbiddenWhy
Test namesThe test emerges from the need, not the other way around
File namesArchitecture reveals itself through iteration
Method signaturesDesign comes from the simplest code
Installation commandsDependencies 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