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.
The three layers of the test automation pyramid: many fast, isolated unit tests at the base, fewer API and integration tests in the middle and a few end-to-end journeys at the top.

At the layer “End-to-end / UI tests”: Fewer tests: more realistic, slower, broaderAt the layer “Unit / component tests”: More tests: faster, isolated, precise

  1. 1End-to-end / UI testsFew · 1 in the exampleA whole user journey through the real interface and every service behind it

    Seconds to minutes each; a failure can come from any part

  2. 2Integration / API testsFewer · 5 in the exampleComponents or services together through a real interface: an HTTP API, a database, a message queue

    Seconds each; catches interface defects unit tests cannot see

  3. 3Unit / component testsMany · 12 in the exampleOne 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.

Try it: Mock API Response Generator

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.00

Try 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.

Try it: Regression Scenario Generator

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.

Pyramid, ice-cream cone and testing trophy side by side: which layer holds most tests, how big the end-to-end layer is and how most checks run.

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

Your task

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.

Material
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
Steps
  1. For each browser test, write down the defect it is meant to catch.
  2. Choose the lowest layer that can see that defect: unit, API or integration, or end-to-end.
  3. 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.
  4. List the questions for the developers and the product owner.
Show the model answer
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

Practise with Mutqan tools

Generate a structured test-case matrix (positive, negative, auth, boundary) from an OpenAPI / Swagger specification.

Automation

Generate complete mock API responses (status, headers, JSON body) for success, list, detail, error and empty cases.

Mocking

Go deeper

Your progress on this topic

Part of the QA Roadmap