The Testing Pyramid
Why cheap tests must outnumber expensive ones, what the pyramid actually claims, the trophy and honeycomb alternatives, and how to tell which shape you really have.
4 min read · updated 19 September 2026
The pyramid is a claim about cost, and everything else about it follows from that. Tests at the bottom are cheap to write, cheap to run and cheap to diagnose. Tests at the top are expensive on all three. Therefore you should have a lot of the first kind and not many of the second.
That is the whole argument. It is not about correctness, purity, or what a "real" unit test is.
#What each level costs
| Unit | Integration | End-to-end | |
|---|---|---|---|
| Runtime | microseconds | 10ms – 1s | 1s – 60s |
| Setup | none | a container or two | a deployed system |
| What a failure tells you | the function | the seam | something is broken |
| Failure when nothing changed | essentially never | occasionally | routinely |
| Cost to fix a false failure | minutes | an hour | half a day |
The last row is the one that decides suite survival. A flaky test at the unit level is a curiosity. A flaky test at the end-to-end level is a tax on every merge, and teams respond to it the same way every time: retries, then quarantine, then deletion.
#What it actually prescribes
The pyramid is ordinal, not numerical. It says each layer should be smaller than the one beneath it. It does not say 70/20/10, and the people who quote that number are usually quoting a blog post quoting a misremembering of Mike Cohn's original diagram, which had no numbers on it.
A sensible reading:
- Base — unit tests. Every branch of every non-trivial decision. Runs in seconds for the whole suite. If running them is not part of your inner loop, they are the wrong shape.
- Middle — integration tests. Every seam where your code meets something it did not write: the database, the HTTP client, the message broker, the file system. One test per behaviour of the seam, not per method that crosses it.
- Top — end-to-end tests. The handful of journeys that, if broken, mean you have no business. Sign up, sign in, buy the thing, get the receipt. Ten to forty of these for most products, not four hundred.
#How to tell what shape you actually have
Do not count test files. Count wall-clock minutes and failure diagnosis time, which is what the pyramid is really about.
# The honest version of the question: where does the time go?
npm test -- --silent 2>&1 | tail -3 # unit
npx playwright test --reporter=line 2>&1 | tail -3 # end-to-endIf the second number is more than about four times the first, you do not have a pyramid regardless of what the file counts say. If more than one in fifty end-to-end runs fails for a reason that is not a real defect, you have an ice cream cone with extra steps.
#The alternatives, and what they get right
The testing trophy (Kent C. Dodds) fattens the middle: a small base of unit tests, a large body of integration tests, a few end-to-end. It is a reasonable modern reading, because the cost assumption underneath the pyramid has genuinely shifted. When the pyramid was drawn, "integration test" meant a shared test environment and a nightly run. With Testcontainers and in-process servers it now means 900 milliseconds. Cheaper middle, fatter middle. The pyramid's own logic gets you there.
The testing honeycomb (Spotify) makes the same move for a microservice estate, where nearly all the risk is in integration and almost none is in any single service's internal logic.
The ice cream cone is what you get by default, and it is worth understanding why: end-to-end tests are the only kind you can write without influencing the design of the code. A team that inherits a codebase, or one whose testers sit outside the engineering team, will produce a cone every time — not through ignorance but through access. See roles and writing testable code; the fix is structural.
#Where to put a given test
The question that resolves most arguments: what is the smallest thing that could be wrong here?
- A discount is calculated as 15% instead of 20% → unit.
- The discount is correct but stored in a
DECIMAL(4,2)column and silently rounds → integration. - The discount is correct and persisted and never rendered on the summary page → end-to-end.
Each of those is a real bug with a natural home. Writing the first as an end-to-end test is the most common mistake in the field: it will pass, it will be slow, and when it eventually fails you will not know which of the nine things it touches was at fault.
#One caveat worth stating plainly
The pyramid describes a steady state, not a starting position. A team with no tests at all and a system in production should usually write a handful of end-to-end journeys first, because those are the tests that tell you whether the refactoring you are about to do broke anything. Build the base afterwards, under the safety net. Starting at the bottom of the pyramid is correct advice for a codebase you are writing and bad advice for one you are rescuing.
Common questions
- What is the ideal ratio of unit to integration to end-to-end tests?
- There isn't one. The commonly quoted 70/20/10 is a rule of thumb, not a finding. What the pyramid actually claims is ordinal — that each level should have fewer tests than the one below it — and the right numbers depend on how much of your risk lives in integration. A thin service that mostly moves data between a queue and a database legitimately has more integration tests than unit tests.
- Is the testing pyramid outdated?
- The shape is not, but two of its assumptions are. When it was proposed, integration tests were slow and awkward because spinning up real dependencies was slow and awkward; Testcontainers and in-process HTTP servers have made mid-level tests dramatically cheaper. The pyramid's cost argument still holds — it is just that the cost of the middle layer dropped, which is why the "testing trophy" shape is a reasonable modern reading of the same principle.
- What is the ice cream cone anti-pattern?
- The pyramid inverted — a large number of end-to-end and manual tests sitting on a thin layer of unit tests. It happens naturally, because end-to-end tests are the easiest kind to write when you do not control the code's design. The symptom is a suite that takes hours, fails unpredictably, and tells you that "checkout is broken" without telling you why.
Runnable samples for this page
last test results ↗- TypeScript
typescript/src/testing-levels/testing-pyramid
Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.
Was this page useful?
Related topics
- Unit TestingWhat a unit actually is, what belongs in a unit test and what does not, and the properties that separate a unit suite people run from one they skip.
- Integration TestingTesting your code against real dependencies — databases, HTTP clients, message brokers — with Testcontainers, and what belongs at this level rather than above or below it.
- End-to-End TestingWhat belongs in an end-to-end suite and what does not, how many journeys are enough, and the practices that keep a browser suite from becoming the thing everyone ignores.
- Flaky TestsWhy tests fail intermittently, the six root causes and how to fix each one, how to detect flakiness deliberately, and what to do with a test you cannot fix today.
- Test StrategyDeciding what to test, at which level, and what not to test at all — written down, so it is a choice rather than an accident.
- End-to-End Code CoverageInstrument the application, run Playwright or Cypress against it, and merge the result with the unit run — the measurement that shows which code only your slowest tests protect.
- Desktop Application TestingAutomating WPF, WinUI, WinForms, Electron and native desktop apps — the accessibility trees, the tooling, and why desktop UI automation is harder than the web.
- Flutter TestingFlutter renders to a canvas with no native accessibility tree, so it brings its own three-layer test harness — unit, widget and integration — plus golden files.