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:
Scenario: User registers successfully
Given I am on the registration page
When I register with email "john@example.com"
Then my account should be createdRun 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:
- RED - Write a failing unit test
- GREEN - Write minimal code to pass
- REFACTOR - Clean up while green
- Check outer loop - Run
pest --bddto see progress
3. Outer Loop: GREEN (Feature Passes)
When all inner loop iterations complete, the BDD test passes:
pest --bdd
# ✓ Scenario: User registers successfully4. 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
| Phase | Action | Command | Expected |
|---|---|---|---|
| BDD RED | Write scenario | pest --bdd | Fail |
| TDD RED | Write unit test | pest tests/Unit | Fail |
| TDD GREEN | Implement | pest tests/Unit | Pass |
| TDD REFACTOR | Clean up | pest tests/Unit | Pass |
| Check BDD | Progress? | pest --bdd | Fail or Pass |
| BDD GREEN | All steps pass | pest --bdd | Pass |
| BDD REFACTOR | Clean up | pest --bdd && pest tests/Unit | Pass |
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.
// ❌ 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): User3. 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() method4. 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 passesPattern 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 passesPattern 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 stepAnti-Patterns to Avoid
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Big steps | Too much in one step | Break into smaller steps |
| Skipping TDD | No unit test coverage | Always TDD inner components |
| Over-engineering | Building for future | Only current test requirements |
| Long RED phases | Too much code before GREEN | Smaller increments |
| Ignoring BDD | Only running unit tests | Check outer loop frequently |