The Test Automation Pyramid
The test automation pyramid is a model for the shape of an automated test suite. Tests are grouped into layers by size: many small, fast, isolated tests at the bottom, fewer tests of parts working together in the middle, and only a few broad end-to-end tests at the top. Mike Cohn introduced it with three layers (unit, service and UI tests), and the ISTQB Foundation syllabus v4.0 uses it to show that different test goals are served by different levels of automation.
The lesson on what to automate decided whether a check is worth automating; the pyramid decides at which level. For a promo code, that means about a dozen unit tests for the discount and date rules, a few API tests for the cart service and its database, and one end-to-end test in the browser that applies the code and pays.
Why it matters
Where a check lives decides what it costs. A browser test that logs in, fills a cart and reads a discount can run for minutes, needs every service and the right data, and when it fails it often reports a missing element rather than a wrong number. The same rule tested as a function runs in milliseconds, fails for one reason and points at the code that broke. A suite built mostly from the expensive kind becomes slow and flaky (its tests pass on one run and fail on the next with no change to the code), so the team stops running it on every change and loses the fast feedback that automation was meant to give.
For a tester, the pyramid is the vocabulary for decisions made every sprint: which checks to automate through the API instead of the UI, what to ask developers to cover with unit tests, and why a fast-growing UI suite is a risk to report rather than progress to celebrate.
The layers and what changes between them
Names and the number of layers vary. Cohn’s pyramid says unit, service and UI; a common version says component, integration and end-to-end, and the ISTQB syllabus notes that both are in use. The idea stays the same: the higher a test sits, the more of the system it exercises in one run, and the fewer such tests the suite needs.
- Unit or component tests (the base, many): one function, class or UI component, with its dependencies replaced by test doubles where needed, as in the lesson on component testing. Each runs in milliseconds, and a failure names the broken unit. Developers usually write them with the code.
- Integration, service or API tests (the middle, fewer): components or services working together through a real interface such as an HTTP API, a database or a message queue. They catch interface defects that unit tests cannot see, such as a renamed field or a record that is never saved, and each runs in seconds.
- End-to-end or UI tests (the top, few): a complete user journey through the real interface and every system behind it. They prove that the pieces are connected for the user, but each needs a full environment and test data, runs for seconds or minutes, and can fail for reasons unrelated to the change.
At the layer “End-to-end / UI tests”: Fewer tests: more realistic, slower, broaderAt the layer “Unit / component tests”: More tests: faster, isolated, precise
- 1End-to-end / UI testsA whole user journey through the real interface and every service behind it
Seconds to minutes each; a failure can come from any part
- 2Integration / API testsComponents or services together through a real interface: an HTTP API, a database, a message queue
Seconds each; catches interface defects unit tests cannot see
- 3Unit / component testsOne function, class or UI component, with test doubles for its dependencies
Milliseconds each; a failure names the broken unit
Counts are from the lesson’s promo code example. Layer names vary: Cohn’s original says unit, service and UI. Exploratory testing is not a layer; people explore on top of the automated checks.
The rule: test at the lowest level that can catch the defect
The practical use of the pyramid is a placement rule. For each check, ask where the defect it targets first becomes visible. A calculation, a validation rule or a date boundary is visible in a unit test. Data that must be stored correctly, or two services that must agree on a format, need an integration or API test. Only a fault in the journey itself, such as a button wired to the wrong action or a page that loses the session after payment, needs an end-to-end test.
Two habits follow. When an end-to-end test or a person finds a defect that a lower test could have caught, add that lower test as well, so the gap is closed where checking is cheapest. And do not repeat a check on every layer: if unit tests already cover the discount boundaries, the UI test needs one value to show that the discount reaches the screen.
A front-end component can often be tested against a mocked API response instead of the real back end, which moves a check from the top of the pyramid to its base.
Worked example: placing the checks for a promo code
Requirement: "SAVE10 gives 10% off orders of 200.00 SAR or more, at most 50.00 SAR off, from 1 to 31 March 2027 (Riyadh time), once per customer." This is the SAVE10 code from the component testing lesson with its full business rule. Boundary value analysis gives the values; the pyramid decides where each check runs.
The twelve checks of amounts, dates and earlier use go to unit tests: they are pure rules, and most of them are boundary values. The API layer adds what a function on its own cannot show: the code is looked up in the real database, a first use is recorded so that a second is refused, and the discount is saved on the order. The single end-to-end test re-checks no boundary; it proves that the field, cart, payment and confirmation are connected for a customer. Written as eighteen browser tests instead, each would log in and fill a cart, and a failure would read "element not found" instead of "500.10 gave 50.01".
Rule: SAVE10 = 10% off orders from 200.00 SAR, at most 50.00 SAR off,
valid 1 to 31 March 2027 (Riyadh time), once per customer
UNIT: discount and validity functions, test doubles for the rest 12 tests
199.99 -> 0.00 200.00 -> 20.00 333.33 -> 33.33
499.90 -> 49.99 500.00 -> 50.00 500.10 -> 50.00 (cap)
28 Feb 23:59:59 -> invalid 1 Mar 00:00:00 -> valid
31 Mar 23:59:59 -> valid 1 Apr 00:00:00 -> invalid
first use -> valid earlier use by this customer -> invalid
API / INTEGRATION: cart service with its real database 5 tests
apply SAVE10 to a 250.00 cart -> accepted, discount 25.00
apply an expired code -> rejected, PROMO_EXPIRED
apply an unknown code -> rejected, PROMO_UNKNOWN
same customer applies it again -> rejected, PROMO_ALREADY_USED
place the order -> discount 25.00 saved on the order
END-TO-END: browser, all services, test payment 1 test
log in, add 250.00 SAR of items, apply SAVE10, see -25.00,
pay with a test card, confirmation shows 225.00Try it: Boundary Number Dataset Generator Try it: OpenAPI → Test Cases
The anti-pattern: the ice-cream cone
The ice-cream cone is the pyramid upside down: a narrow tip of unit tests, a few integration tests, a wide band of automated UI tests and, on top, a large scoop of manual regression testing. Alister Scott popularised the name, and "The Practical Test Pyramid" warns that such a suite is hard to maintain and takes far too long to run.
A cone usually grows by accident: automation starts with people who can only reach the UI, every fixed bug gets a new browser test, and nobody asks which rules unit tests already cover. Getting out is gradual. For each slow or flaky UI test, name the defect it should catch, move that check to the lowest layer that can catch it, keep one journey per critical flow, and agree with developers which rules the unit tests own.
Symptoms you can see in a team’s pipeline:
- The regression run takes hours, so it runs nightly instead of on every change.
- Most failures are timeouts and missing elements, and people rerun the suite instead of investigating.
- One change to a page breaks dozens of tests at once.
- Manual regression before each release still takes days, because nobody trusts the automation.
Another shape: the testing trophy
The pyramid is not the only model. Kent C. Dodds proposed the testing trophy for JavaScript web applications: static analysis (type checking and linting) at the base, then unit tests, then integration tests as the largest layer, and a small top of end-to-end tests. His argument is that the more a test resembles the way the software is used, the more confidence it gives, and that integration tests offer the best balance between that confidence and the cost of running and maintaining tests.
The two shapes disagree less than they seem. Both keep the end-to-end layer small, both reject the ice-cream cone, and both ask you to place each check where it gives the most confidence for its cost. What differs is what "integration" means: in a front-end application it can be a few components rendered together against a mocked API, which can run quickly without a real browser or back end. Choose the shape from your architecture, and judge it by feedback time and by the defects that escape, not by how closely it matches a picture.
Test pyramidModel
Many unit tests, fewer integration tests, few end-to-end tests
- Largest layer:
- Unit tests
- End-to-end layer:
- Small
- Most checks run as:
- Isolated unit tests, in milliseconds
- Named by:
- Mike Cohn
Ice-cream coneAnti-pattern
Few unit tests, many UI tests and a large scoop of manual regression on top
- Largest layer:
- UI tests and manual regression
- End-to-end layer:
- Large
- Most checks run as:
- Browser runs and manual passes, in hours or days
- Named by:
- Popularised by Alister Scott, as a warning
Testing trophyVariant
Static analysis at the base, mostly integration tests, few end-to-end tests
- Largest layer:
- Integration tests
- End-to-end layer:
- Small
- Most checks run as:
- Components tested together, often against a mocked API
- Named by:
- Kent C. Dodds, for JavaScript web applications
Common mistakes
- Treating the pyramid as a fixed percentage split per layer instead of a direction: most checks low, few at the top.
- Writing an end-to-end test for every acceptance criterion, including rules that a unit or API test could check in milliseconds.
- Repeating the same check on every layer, so one defect fails several tests and each change costs several updates.
- Fixing a defect found by a UI test without adding the lower-level test that would have caught it sooner.
- Counting exploratory testing as part of the cone to remove: what should shrink is the manual repetition of scripted regression checks, not exploration.
- Never asking developers which rules their unit tests already cover, and then duplicating those checks through the browser.
The QA perspective
On a team, the pyramid shows up in planning and review conversations. When a story is refined, list its checks and agree with the developers which layer owns each one: rules and calculations in unit tests, contracts and data in API tests, one journey per critical flow end to end. Record the decision next to the test cases, for example in a "level" field, so the next person does not add the same check through the browser.
Watch the shape of the suite over time. Useful signals are the time from commit to result, how many failures come from the environment or timing rather than from defects, and which escaped defects a lower test could have caught. Bring these to the retrospective, and when you propose a new UI test, be ready to say what it catches that no lower test can.
Practice exercise
Reshape a checkout suite that has become an ice-cream cone
A team’s checkout regression suite has 2 unit tests, 3 API tests and the 10 browser tests below; the browser tests alone take about 40 minutes per run and often fail on timeouts. The rules: delivery is free when the item total before VAT is 150.00 SAR or more, otherwise it costs 15.00 SAR; VAT of 15% is charged on items and delivery; cash on delivery is offered only for order totals up to 1,000.00 SAR.
Reshape the suite into a pyramid: place every check at the lowest layer that can catch its defect, decide which end-to-end journeys remain, and list what you need to ask the developers.
checkout.spec (browser): each test logs in, fills a cart, opens checkout
B1 item total 149.99 -> delivery fee 15.00
B2 item total 150.00 -> free delivery
B3 item total 150.01 -> free delivery
B4 items 100.00 + delivery 15.00 -> VAT 17.25
B5 items 200.00, free delivery -> VAT 30.00
B6 order total 1,000.00 -> cash on delivery offered
B7 order total 1,000.01 -> cash on delivery not offered
B8 pay by test card -> order confirmed
B9 choose cash on delivery -> order confirmed, "awaiting payment"
B10 delivery service unavailable -> error message on the checkout page- For each browser test, write down the defect it is meant to catch.
- Choose the lowest layer that can see that defect: unit, API or integration, or end-to-end.
- Keep end-to-end tests only for journeys that can break between the browser and the back end, and let one journey cover several assertions.
- List the questions for the developers and the product owner.
Show the model answer
Unit: B1 to B7 are rules on numbers. The defects they target are a wrong free-delivery threshold (B1 to B3), VAT that is miscalculated or not charged on delivery (B4, B5) and a wrong cash-on-delivery limit (B6, B7). Move them to unit tests of the delivery-fee, VAT and payment-options functions, where adding more boundaries is cheap: for example 0.01, and a total whose VAT needs rounding, to settle the rounding rule.
API or integration: one test that the checkout API returns the fee, VAT and payment options for a given cart, to prove it uses those functions (one value each is enough), and one for B10 with the delivery service replaced by a stub that returns an error, to check that checkout answers with a defined error instead of crashing. The message on the page can be covered by a component test of the checkout page with a mocked error response.
End-to-end: keep B8 and B9, the card and cash-on-delivery journeys, because each crosses the browser, checkout, payment and order services. Let each also assert the total on the confirmation page, so the user-visible result of the calculation is checked once.
Result: about ten unit and component tests (the seven moved checks, the two new boundaries and the error-message component test), two API tests and two end-to-end tests instead of ten browser tests. Questions: Do existing unit tests already cover any of these rules? Are the fee and VAT calculated on the server or in the browser (if in the browser, a front-end unit or component test is needed too)? Is the 1,000.00 SAR limit before or after VAT? Can the test environment simulate the delivery service failing?
Frequently asked questions
Is there a correct ratio of unit, integration and end-to-end tests?
No. The pyramid gives a direction, not a quota: most checks low, few at the top. The real ratio depends on the architecture: a service full of business rules ends up mostly with unit tests, while a thin layer that connects other systems needs more integration tests. Judge the suite by feedback time, the number of flaky tests and the defects that escape.
Are unit tests the tester’s job?
Developers usually write them with the code, as in the lesson on component testing. The tester reviews what they cover, suggests boundary and negative cases, and avoids repeating them on higher layers. In some teams testers write them too.
Where do manual and exploratory testing fit?
Outside the automated pyramid. Some well-known versions draw manual or exploratory testing as a cloud above it: automation checks what you anticipated, exploration finds what you did not. What the pyramid reduces is the manual repetition of scripted regression checks.
Pyramid or trophy: which should my team use?
Use the reasoning, not the picture: place each check where it is cheapest for the confidence it gives. Front-end-heavy JavaScript applications, where component integration tests are fast, often end up looking like a trophy; back-end services rich in business rules look like a pyramid. Both keep the end-to-end layer small.
References
- ISTQB Certified Tester Foundation Level (CTFL) v4.0The foundation syllabus of software testing: test levels and types, test design techniques, test management and tools.
- The Practical Test Pyramid (Ham Vocke)A widely cited explanation of how to balance unit, integration, contract and UI tests in an automated test portfolio.
- Agile Testing: A Practical Guide for Testers and Agile Teams (Lisa Crispin, Janet Gregory)The agile testing quadrants and how testers work inside a delivery team — the basis of "testing at the right layer".
Practise with Mutqan tools
Generate boundary values around limits: min-1, min, min+1, max-1, max, max+1 for integers of any width.
Generate a structured test-case matrix (positive, negative, auth, boundary) from an OpenAPI / Swagger specification.
Generate complete mock API responses (status, headers, JSON body) for success, list, detail, error and empty cases.
Generate regression scenarios from a change description and a list of affected and related modules.
Go deeper
- Manual vs Automated TestingDeciding what to automate: the pyramid and a simple rule
- UI Test AutomationA practical strategy: what to automate at the UI level
- CI/CD TestingTest stages in a pipeline and why the order matters
- Flaky TestsWhat makes UI tests flaky — timing, shared state, data, environment, third parties — how to diagnose them, and a sane retry and quarantine policy.
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.