The testing triangle
Also called the test pyramid, popularised by Mike Cohn in Succeeding with Agile (2009). Write many small, fast, isolated tests at the bottom and a few broad, slow, realistic tests at the top. The higher a test sits, the more it costs to write, run and debug.
The three layers
| Layer | Scope | Speed | Catches | Example tools |
|---|---|---|---|---|
| Unit | One function or class; dependencies faked | Milliseconds | Logic errors, edge cases, regressions in isolated code | Vitest, Jest, pytest, JUnit |
| Integration | Several components, or code plus a real dependency (database, HTTP, queue) | Seconds | Contract mismatches, wiring, serialisation and query bugs | Testcontainers, Supertest, Pact |
| UI / E2E | The whole system through the UI or public API, as a user would | Seconds to minutes | Broken user journeys, configuration and deployment problems | Playwright, Cypress, Selenium |
Why the shape works
- Feedback speed. Thousands of unit tests run in seconds, so developers run them on every save.
- Cost. Lower tests are cheaper to write and survive refactors of unrelated code.
- Reliability. Fewer moving parts mean fewer flaky failures from timing, network or test data.
- Diagnosis. A failing unit test points at one function. A failing E2E test points at the whole stack.
Anti-patterns
The ice-cream cone inverts the pyramid: most effort goes into manual and E2E tests, with few unit tests underneath. The pipeline is slow, failures are flaky and hard to locate, and teams start ignoring red builds.
The hourglass has plenty of unit and E2E tests but almost no integration tests, so the seams between components, where many real bugs live, go untested.
Duplicate coverage asserts the same rule at every layer. It triples the maintenance cost without adding confidence.
Variants
Testing trophy (Kent C. Dodds) adds static analysis as the base and makes integration tests the largest layer. It suits front-end apps, where most bugs appear when components work together.
Test honeycomb (Spotify) centres microservice testing on integration tests of each service through its real interfaces, with few implementation-detail tests.
The right shape follows your architecture. The underlying rule does not change: prefer the fastest, most isolated test that can still catch the defect.