Skip to content

The Workflow

The Double Loop workflow alternates between the outer BDD loop and the inner TDD loop. This page explains the conceptual process - for a hands-on example, see the Tutorial.

Visual Overview

┌──────────────────────────────────────────────────────────────────┐
│                        OUTER LOOP (BDD)                          │
│                                                                  │
│  ┌─────────┐    ┌─────────────────────┐    ┌─────────┐          │
│  │  RED    │───►│  Enter Inner Loop   │───►│  GREEN  │          │
│  │ (BDD)   │    │      (TDD)          │    │  (BDD)  │          │
│  └─────────┘    └─────────────────────┘    └────┬────┘          │
│       ▲                                         │               │
│       │              ┌──────────┐               │               │
│       └──────────────│ REFACTOR │◄──────────────┘               │
│                      └──────────┘                               │
└──────────────────────────────────────────────────────────────────┘

The Process

1. Outer Loop: RED (Write Failing Feature)

Start with a Gherkin scenario describing what the user wants:

gherkin
Scenario: User registers successfully
  Given I am on the registration page
  When I register with email "john@example.com"
  Then my account should be created

Run pest --bdd → Test fails (step not found or code missing)

2. Inner Loop: Build What's Needed

Enter the TDD cycle to implement each missing piece:

┌─────────────────────────────────────┐
│         INNER LOOP (TDD)            │
│                                     │
│   ┌─────┐   ┌───────┐   ┌────────┐  │
│   │ RED │──►│ GREEN │──►│REFACTOR│  │
│   └──┬──┘   └───────┘   └───┬────┘  │
│      │                      │       │
│      └──────────────────────┘       │
│              repeat                 │
└─────────────────────────────────────┘

For each missing component:

  1. RED - Write a failing unit test
  2. GREEN - Write minimal code to pass
  3. REFACTOR - Clean up while green
  4. Check outer loop - Run pest --bdd to see progress

3. Outer Loop: GREEN (Feature Passes)

When all inner loop iterations complete, the BDD test passes:

bash
pest --bdd
# ✓ Scenario: User registers successfully

4. Refactor with Confidence

With both unit and behavior tests passing, refactor safely:

  • Extract services, repositories, value objects
  • Improve naming and structure
  • Run all tests to verify nothing broke

5. Repeat with Next Scenario

Return to step 1 with the next scenario.

The Rhythm

PhaseActionCommandExpected
BDD REDWrite scenariopest --bddFail
TDD REDWrite unit testpest tests/UnitFail
TDD GREENImplementpest tests/UnitPass
TDD REFACTORClean uppest tests/UnitPass
Check BDDProgress?pest --bddFail or Pass
BDD GREENAll steps passpest --bddPass
BDD REFACTORClean uppest --bdd && pest tests/UnitPass

Key Rules

1. Test Must Fail First

Never Skip RED

A test that passes immediately (before writing code) indicates:

  • Test is incorrect
  • Feature already exists
  • Test not added to suite

Always see the failure before making it pass.

2. Minimal Code Only

Write only enough code to make the current test pass. No more.

php
// ❌ Don't anticipate future needs
public function register(string $email, string $password, ?string $role = null): User

// ✓ Only what the test requires
public function register(string $email, string $password): User

3. Let Tests Guide You

The test output tells you exactly what to do next:

Step not found: "I am on registration page"
→ Create the step definition

Class "UserService" not found
→ Create the UserService class

Call to undefined method UserService::register()
→ Add the register() method

4. Check Outer Loop Frequently

After each TDD GREEN, run pest --bdd to see progress. This keeps you oriented toward the user goal.

Common Patterns

Pattern 1: Step → Service → Model

BDD Step fails
  → Create step definition
    → Step calls Service (doesn't exist)
      → TDD: Create Service
        → Service uses Model (doesn't exist)
          → TDD: Create Model
        → Service complete
    → Step works
  → BDD passes

Pattern 2: Multiple TDD Cycles per Step

One BDD step may require several TDD iterations:

"When I complete checkout"
  → TDD: PaymentService
  → TDD: InventoryService
  → TDD: EmailService
  → TDD: OrderService (orchestrates all)
→ Step passes

Pattern 3: Reusing Existing Code

Sometimes a step already has supporting code:

BDD: "Given a user exists"
  → UserFactory already has #[Given('a user exists')]
  → Step passes immediately
→ Move to next step

Anti-Patterns to Avoid

Anti-PatternProblemSolution
Big stepsToo much in one stepBreak into smaller steps
Skipping TDDNo unit test coverageAlways TDD inner components
Over-engineeringBuilding for futureOnly current test requirements
Long RED phasesToo much code before GREENSmaller increments
Ignoring BDDOnly running unit testsCheck outer loop frequently

Released under the MIT License.