# The test pyramid

> Many fast unit tests, fewer integration tests, and a handful of end-to-end tests — and why the opposite shape produces slow, flaky suites.

Concept · Testing practice · https://levelcode.org/concepts/test-pyramid

The **test pyramid**, popularised by Mike Cohn, describes a healthy balance of automated tests by scope:

```text
       /\        End-to-end: a few critical user journeys, full stack
      /  \
     /----\      Integration: real database, HTTP, queues — module boundaries
    /      \
   /--------\    Unit: functions and types in memory — most tests live here
```

## Why that shape

Test cost rises with scope:

| | Unit | Integration | End-to-end |
| --- | --- | --- | --- |
| Speed | milliseconds | tens to hundreds of ms | seconds to minutes |
| Failure points to | one function | one boundary | anywhere |
| Flakiness | rare | occasional | common |
| Confidence the whole works | low | medium | high |

Unit tests are cheap and precise, so they cover the logic and edge cases. Integration tests prove the pieces really fit — SQL queries against a real database, HTTP handlers through the router, a client against a test server. End-to-end tests confirm the most important journeys (sign-up, checkout) work for a real user, and stop there, because each one is slow and fails for many reasons.

The anti-pattern is the **ice-cream cone**: mostly manual and end-to-end tests, few unit tests. Suites take an hour, fail randomly, and teams learn to rerun until green — which means they stop trusting red.

## Variations

The pyramid is a heuristic, not a law. Kent C. Dodds' **testing trophy**, popular for frontends, puts the bulk in integration tests — rendering components with their real children and checking what the user sees — because isolated unit tests of UI components often test implementation details. Services with little logic but many integrations may also be integration-heavy.

The principle underneath holds everywhere: push each test to the **lowest level that can catch the bug**, keep the suite fast enough to run on every change, and treat a flaky test as a bug to fix, not noise to ignore. See [test doubles](https://levelcode.org/concepts/test-doubles) for isolating units, and the [TDD lesson](https://levelcode.org/roadmaps/software-engineer/tdd).
