← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Test-Driven Development (TDD)

Testing PHP 5.0+ Intermediate
debt(d7/e5/b5/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate no automated detection ('automated: no') and the code_pattern requires a human reviewer to notice that test files were written after implementation or that no TDD workflow was followed. Tools like phpunit/pest/phpstan can confirm tests exist but cannot determine whether the red-green-refactor cycle was followed — only careful code review or retrospective analysis of commit history could reveal TDD abandonment.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes a workflow change (write failing test first, then minimum code, then refactor) rather than a single-line patch. Correcting a codebase where TDD was abandoned means retrofitting tests across multiple files to restore coverage and design intent — this goes beyond a simple parameterised fix but falls short of a full cross-cutting architectural rework.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). TDD applies broadly across web, cli, and queue-worker contexts (per applies_to). When TDD is misapplied or abandoned, the resulting tight coupling and bolted-on tests slow down many future work streams — every feature addition or refactor is more expensive. However, it doesn't rise to b7 because TDD adoption or abandonment doesn't architecturally constrain every change in the way a foundational framework choice would.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field directly identifies the canonical wrong belief: that TDD means mechanically writing tests before every line of code forever. The common_mistakes reinforce this — developers conflate TDD with waterfall test-first, skip refactor steps, or write overly large cycles. This is a well-documented misunderstanding that most developers encounter and must correct, but it doesn't contradict analogous concepts elsewhere in a catastrophic way.

About DEBT scoring →

Also Known As

TDD test-first development red-green-refactor

TL;DR

A development practice where tests are written before production code, driving design through the Red-Green-Refactor cycle.

Explanation

TDD follows a tight cycle: (1) write a failing test (Red), (2) write the minimum code to make it pass (Green), (3) refactor for quality while keeping tests green. Writing tests first forces clarity about requirements before implementation, produces naturally testable code (small, focused, with injected dependencies), and creates a comprehensive test suite as a by-product. TDD is a design activity as much as a testing activity — the pain of writing tests signals design problems. Outside-in TDD (starting from integration/acceptance tests) is called ATDD or BDD.

Diagram

flowchart LR
    RED[Write failing test<br/>RED] -->|implement code| GREEN[Make test pass<br/>GREEN]
    GREEN -->|improve code| REFACTOR[Refactor<br/>BLUE]
    REFACTOR -->|write next test| RED
    subgraph Benefits
        DESIGN[Forces thinking about API<br/>before implementation]
        COVERAGE2[100pct coverage by default<br/>only code tests need]
        SAFETY[Refactor safely<br/>tests catch regressions]
    end
    subgraph TDD_vs_Tests_After
        AFTER[Tests after - verify it works]
        TDD2[TDD - design tool + verification]
    end
style RED fill:#f85149,color:#fff
style GREEN fill:#238636,color:#fff
style REFACTOR fill:#1f6feb,color:#fff

Common Misconception

TDD means writing tests before every line of code, forever. TDD is a design technique — the tests drive you toward loosely coupled, testable code. Many experienced practitioners use it selectively for complex logic rather than mechanically for all code.

Why It Matters

TDD forces you to design the API of your code before implementing it — tests written first expose awkward interfaces immediately, when they are cheap to fix. The test suite that emerges is comprehensive by construction, not bolted on after.

Common Mistakes

  • Writing all tests first, then all implementation — TDD is red/green/refactor per small cycle, not a waterfall.
  • Skipping the refactor step — without it TDD produces working but messy code.
  • Writing tests that are too large — each cycle should test one specific behaviour.
  • Abandoning TDD when time pressure hits — this is exactly when having tests matters most.

Code Examples

✗ Vulnerable
// Test-last approach — code written first, then retrofitted with tests:
class UserService {
    public function create(array $data): User {
        // 100 lines of code written without tests
        // Now write tests that 'fit' the existing code
        // Tests describe implementation, not behaviour
    }
}
// TDD: test first → drives minimal implementation → refactor safely
✓ Fixed
// RED: write a failing test first
public function test_discount_applied_for_vip(): void {
    $pricing = new PricingService();
    $price   = $pricing->calculate(product: $product, customer: $vip);
    $this->assertEquals(90.00, $price); // FAILS — method doesn't exist yet
}

// GREEN: write the minimum code to pass
class PricingService {
    public function calculate(Product $product, Customer $customer): float {
        $price = $product->basePrice;
        if ($customer->isVip()) $price *= 0.9;
        return $price;
    }
}

// REFACTOR: clean up while tests stay green

Added 15 Mar 2026
Edited 22 Mar 2026
Views 78
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 3 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 3 pings W 1 ping T 2 pings F 3 pings S 10 pings S 5 pings M 1 ping T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Scrapy 23 Perplexity 11 Amazonbot 7 Ahrefs 6 SEMrush 6 Google 5 Unknown AI 3 Claude 2 ChatGPT 1 Bing 1 Meta AI 1 PetalBot 1
crawler 63 crawler_json 4
DEV INTEL Tools & Severity
🔵 Info ⚙ Fix effort: High
⚡ Quick Fix
Write a failing test first, then write the minimum code to make it pass, then refactor — the test defines the API before the implementation exists
📦 Applies To
PHP 5.0+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
Test files written after implementation; test coverage added retroactively with no TDD workflow
Auto-detectable: ✗ No phpunit pest phpstan
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: High Context: File Tests: Update


✓ schema.org compliant