The Testing Pyramid: Layers, Limits, and Where AI Changes It
The testing pyramid is a model for structuring a test suite by allocating most tests to the fastest, most granular layer and progressively fewer to the slower, broader layers. Unit tests form the wide base, integration tests the middle, and end-to-end tests the narrow top. The shape reflects cost and speed. A unit test runs in milliseconds and fails for one clear reason. An end-to-end test takes minutes and can fail for dozens.
Mike Cohn introduced the model in Succeeding with Agile in 2009, and it has been argued over ever since. The arguments are the interesting part, because the pyramid describes a cost structure that AI-assisted testing is actively changing.
This guide covers each layer in practical detail: what to test, who runs it, when, and with which tools. It then covers the parts most guides skip. Where the model breaks down, what changes when AI writes your tests, and why the top of the pyramid is the tier almost every team under-covers.
What is the testing pyramid?
The pyramid is a heuristic about proportion rather than a rulebook. Its central claim: when you have a choice about where to test something, test it at the lowest layer that can meaningfully answer the question.
| Layer | What it tests | Speed | Who usually runs it | Share of suite |
|---|---|---|---|---|
| Unit | One function or component in isolation | Milliseconds | Developers | The majority |
| Integration | How components, services and APIs behave together | Seconds | Developers and QA engineers | A substantial minority |
| End-to-end | A complete user journey through the real system | Minutes | QA engineers | The smallest number |
Why is it a pyramid and not a rectangle?
Three costs rise as you move up: execution time, maintenance burden, and flakiness. A test that touches a network, a database and a rendering engine has three additional ways to fail for reasons unrelated to your code.
The deeper reason is diagnostic precision. When a unit test fails, you know which function broke. When an end-to-end test fails, you know something in a chain of twenty components broke, and finding out which one costs an engineer an afternoon. The pyramid pushes coverage downward because low-layer failures are cheap to interpret.
Flaky high-layer tests are worse than no tests at all. A suite that fails randomly gets ignored, and an ignored suite provides no signal.
Do the layers of the pyramid overlap?
Yes, and some overlap is unavoidable. An integration test covering a checkout flow inevitably touches behaviour an end-to-end test also exercises. The problem isn't overlap itself but unmanaged overlap, where the same behaviour gets verified three times at three different costs.
The practical rule: when two layers can answer the same question, keep the test at the lower one and delete the duplicate above it. Define what each layer means for your team explicitly, because industry naming is inconsistent enough that two engineers will otherwise classify the same test differently.
Variations on the model
Several variants circulate. All of them respond to the same criticism, which is that three layers is too coarse.
- Static analysis as the base. Linting, type checking and static security scanning sit below unit tests. Faster still, and they catch whole categories of defect before a test runs at all.
- A manual or exploratory tier above the top. Recognises that some questions cannot be automated at any layer.
- The testing trophy. A fatter integration middle, argued for service-heavy architectures.
- The testing honeycomb. Similar reasoning, aimed specifically at microservices.
Unit testing: the base of the pyramid
Unit tests verify one unit of behaviour in isolation, with dependencies replaced by stubs or mocks. They should be fast enough to run on every save and deterministic enough that a failure always means something real.
Who performs unit tests?
Developers, almost always. Unit tests get written alongside the code they cover, by the person writing it, because they require knowledge of the internal design. Under test-driven development they come first, so the test defines the interface before the implementation exists.
When should you run unit tests?
Continuously during development. On save locally, and on every commit in CI. If your unit suite is too slow to run on save, it has stopped being a unit suite and something in it is reaching outside its boundary.
What to test at the unit layer
- Business logic, calculations and validation rules
- State transitions and conditional branches
- Error handling for malformed or missing input
- Boundary conditions: the first valid value, the last, and the ones either side
- The public interface of a class or module, not its private internals
Unit testing examples
- Password validation. Does a rule requiring one uppercase character reject a lowercase-only string, and accept a valid one?
- Currency rounding. Does a total of 10.005 round the way your finance team expects, in every supported currency?
- Date parsing. Does 03/04/2026 resolve correctly given a locale, and does an invalid date fail rather than silently coerce?
- Discount logic. Do two stacked promotions combine as specified, and what happens at 100%?
Unit testing best practices
- One behaviour per test. A test asserting five things tells you almost nothing when it fails.
- Name tests after the behaviour, not the method. rejectsPasswordWithoutUppercase beats testValidate3.
- Keep them deterministic. No real clocks, no random values, no network calls. Inject anything non-deterministic.
- Don't over-mock. The most common failure at this layer is mocking so heavily that the test verifies your mocks rather than your code. If a test still passes after a dependency's contract changes, it was testing the wrong thing.
- Test the interface, not the implementation. A test that breaks whenever you refactor without changing behaviour is a maintenance cost, not a safety net.
Unit testing tools
| Tool | Language or platform |
|---|---|
| JUnit | Java and the JVM |
| TestNG | Java, with more configuration and parallel execution than JUnit |
| NUnit, xUnit | .NET languages |
| pytest, unittest | Python |
| Jest, Vitest, Mocha | JavaScript and TypeScript |
| RSpec | Ruby |
| PHPUnit | PHP |
| Unity | C, particularly embedded software |
More on tooling at this layer in our guide to automation testing.
Integration testing: the middle of the pyramid
Integration tests verify that components work together, and that the contract each side assumed actually holds. A large share of production incidents originate here, because unit tests pass on both sides of an interface while the interface itself is wrong.
The naming is genuinely contested. The same tests get called integration tests, service tests, component tests, or subsystem tests depending on who you ask. Agree definitions inside your team rather than assuming the industry has settled them.
Who performs integration tests?
Both developers and QA engineers, with the split varying by organisation. Developers typically own tests between modules they wrote. QA engineers more often own tests spanning services, third-party integrations, and data flows that cross team boundaries.
When should you run integration tests?
After the components involved pass their own unit tests, and as a stage in your CI pipeline on every merge. They're slower than unit tests, so they usually run after the unit suite passes rather than alongside it.
What to test at the integration layer
- API request and response contracts, including error responses
- Database queries against a real schema rather than a mock
- Serialisation and deserialisation, so you know data survives the round trip intact
- Message queue publishing and consumption
- Third-party service integration, including how you behave when it's slow or down
- Authentication and authorisation across service boundaries
Contract testing
Contract testing deserves singling out. Rather than spinning up every service to test them together, contract tests verify that each service honours the interface its consumers depend on, with each side testing against a shared contract independently.
In a system of independently deployed services this is often more valuable than end-to-end coverage and considerably cheaper to run. It catches the failure mode that matters, which is one team changing a response shape another team relies on, without requiring the whole system to be running.
Integration testing examples
- Checkout flow. A login service, cart module and payment gateway exchanging data correctly through a purchase.
- Permissions propagation. One user updates a record; another with appropriate access sees the change immediately.
- Microservice messaging. A transaction service triggers a receipt email and updates a balance service, with all three staying consistent.
- Third-party failure. The payment provider returns a timeout. Does your service retry, queue, or surface a usable error?
Integration testing best practices
- Test in small groups. Two or three components at a time keeps failures diagnosable.
- Use real dependencies where you can. A real database in a container beats a mocked one. The whole point of this layer is verifying assumptions about real behaviour.
- Test negative paths deliberately. Most integration defects live in error handling rather than the happy path.
- Mock only what you can't run. Third-party services you don't control are legitimate mocking candidates. Your own database isn't.
- Keep them separate from unit tests. Different speeds, different failure meanings, different pipeline stages.
Integration testing tools
- Testcontainers runs real databases, queues and services in throwaway containers for the duration of a test.
- Pact handles consumer-driven contract testing across services.
- Postman and REST Assured validate API requests and responses.
- WireMock stubs and simulates HTTP services, including failure modes.
- Spring Boot Test covers integration testing within the Spring ecosystem.
Our guide to API test cases covers this layer in more depth.
End-to-end testing: the top of the pyramid
End-to-end tests exercise a complete journey through the real system, the way a user would. Sign up, search, add to basket, pay, receive confirmation. They run against a fully deployed application through its interface.
Who performs E2E tests?
QA engineers typically own automated end-to-end suites, since they require a view of the whole product rather than any one component. Developers are less involved at this layer, which is itself a risk, because an end-to-end failure often needs developer knowledge to diagnose.
When should you run E2E tests?
After integration tests pass, and before release. Because they're slow, many teams run the full suite nightly or pre-release and a smaller smoke subset on every merge. Our guide to smoke testing covers that subset.
What to test at the E2E layer
Keep this layer small and reserve it for journeys where failure is unacceptable, typically the paths that produce revenue or handle sensitive data. Every additional end-to-end test adds pipeline runtime and another candidate for flakiness.
- The two or three journeys that must never break
- Flows crossing several systems where no lower layer sees the whole path
- Critical authentication and payment paths
- Anything with a regulatory or contractual consequence if it fails
E2E testing best practices
- Ruthlessly limit the count. Ten reliable end-to-end tests beat two hundred flaky ones.
- Fix flakiness immediately. Quarantine any test that fails randomly and repair it before adding more.
- Use stable selectors. Test-specific attributes rather than CSS classes or DOM position, which change with every redesign.
- Make tests independent. Any test depending on another having run first will fail unpredictably under parallel execution.
- Control your test data. Tests sharing mutable state are the most common source of intermittent failure at this layer.
E2E testing tools
- Playwright offers cross-browser automation with strong handling of waits and parallelism.
- Cypress provides browser testing with time-travel debugging.
- Selenium is the long-established option, with the widest language and browser support.
- Appium handles native and hybrid mobile app automation.
- WebdriverIO covers Node-based automation across web and mobile.
Above the pyramid: what automation cannot answer
This is the tier most guides skip, and the one where most teams have their real gap.
An automated end-to-end test verifies that a journey completes. It cannot tell you whether the journey is acceptable. Those are different questions, and only one of them can be asserted.
A payment flow can pass every assertion in your suite and still fail a real user, because:
- The confirmation copy reads as a rejection once translated into Portuguese
- The card form rejects a valid local card number format
- A sticky submit button sits under the keyboard on one popular handset
- The flow times out on a real 3G connection but passes on office wifi
- 03/04/2026 is ambiguous in one market and simply wrong in another
- A required field expects a postcode format that doesn't exist in the country you just launched in
None of those are assertion failures. Every one is a defect that reaches users. They surface when someone who didn't build the product uses it, on a device you don't own, in a market you don't live in.
What sits at this tier
- Exploratory testing is unscripted investigation that finds defects nobody wrote a case for.
- Real-device testing uses actual handsets, OS versions and carrier networks rather than emulators.
- Localization testing checks whether translated copy, formats and conventions work in each market.
- Accessibility testing covers screen readers, magnification and keyboard navigation, where automated tools catch only a fraction of real barriers.
- Usability testing establishes whether the flow makes sense to someone encountering it for the first time.
These are a distinct activity, not more tests in the same pipeline. They don't run on every merge and they don't produce pass/fail booleans, which is precisely why they catch what the pipeline can't.
Benefits of the testing pyramid
- Fast feedback where it matters. Most failures surface in seconds rather than at the end of a nightly run.
- Cheap diagnosis. Failures land close to their cause, so an engineer knows where to look.
- Lower total cost. Fewer slow tests means shorter pipelines and less time maintaining brittle suites.
- Earlier defect detection. Problems found during development cost a fraction of problems found in production.
- A shared vocabulary. The model gives a team a way to argue productively about where a test belongs.
- Fits agile testing cadence. Short cycles need a suite that runs inside a sprint rather than consuming one.
When the testing pyramid doesn't apply
Treating the model as doctrine causes its own problems. Most critiques of the pyramid are really objections to applying it where it doesn't fit.
Four cases where the shape is wrong
- Your system is mostly integration. A service that primarily orchestrates other services has little isolated logic to unit test. Forcing a wide base produces tests of your mocks.
- The product is the interface. For a design tool or a game, most risk lives in interaction and rendering. Coverage has to sit where risk sits.
- You're testing someone else's system. With no code access, unit testing isn't available and everything happens at the top by definition.
- You inherited a legacy codebase. Retrofitting unit tests to untestable code is often slower than covering critical journeys end-to-end first, then refactoring behind that safety net.
Testing pyramid vs testing trophy vs ice cream cone
| Model | Shape | Fits |
|---|---|---|
| Testing pyramid | Wide unit base, narrow E2E top | Applications with substantial isolated business logic |
| Testing trophy | Fat integration middle | Service-heavy or orchestration-heavy architectures |
| Testing honeycomb | Integration-dominant | Microservices, where most risk sits between services |
| Ice cream cone | Wide manual and E2E top, thin base | Nobody. This is the anti-pattern, not a strategy |
The underlying principle survives all of them. Test at the lowest layer that can answer the question, and keep the expensive layers focused on what only they can reach.
How AI-generated tests change the pyramid
The pyramid's shape was always an argument about cost. AI-assisted testing has lowered the cost of writing tests at every layer, which has led some teams to conclude the shape no longer matters. That conclusion is half right, and the half that's wrong is expensive.
What genuinely changed
- Writing tests got cheap. Generating unit tests from a function signature is now near-free, so there's no longer a good excuse for a thin base.
- Maintenance got cheaper at the top. Self-healing tools re-identify elements when selectors change, removing much of the historical maintenance cost of end-to-end suites.
- Coverage gaps are easier to find. AI is effective at spotting untested branches and proposing cases a human would skip.
What did not change
- Running tests still costs time. Generation is cheap. Execution is not. A thousand generated end-to-end tests still add hours to your pipeline.
- Diagnostic cost is unchanged. A failure at the top still means searching a long chain for the cause. AI writes the test; it doesn't tell you which of twenty components broke.
- Generated tests can encode the bug. A test written from the implementation asserts what the code does rather than what it should do. Cheap generation makes it easy to build a large suite that passes reliably and verifies nothing, and that risk grows with volume rather than shrinking.
- Judgement is still judgement. No generated test knows whether translated copy reads oddly, or whether a flow feels trustworthy enough to hand over card details.
And a new layer to test
If your product now contains AI features, they need testing too, and conventional test design doesn't apply cleanly because the output isn't deterministic. The same input can produce several valid responses, so there's no single expected result to assert against. Testing shifts from exact-match assertions to evaluating whether output falls within acceptable boundaries, which is a human judgement by definition.
So AI makes the base easier to build properly and the top cheaper to maintain. It doesn't remove the need for the shape, and it doesn't touch the judgement the top tier depends on. If anything the pyramid matters more now, because it has become trivially easy to generate a suite so large that nobody reads its output.
What makes a good test at any layer?
The Arrange, Act, Assert pattern applies at every level of the pyramid:
- Arrange the data, state and dependencies the test needs
- Act by performing the single action under test
- Assert that the outcome matches expectation
A test that's hard to fit into that shape is usually testing more than one thing. Two additional properties matter regardless of layer. A test should fail for exactly one reason, and its name should tell you what broke without opening the file.
How to apply the pyramid to your suite
- Measure your current shape. Count tests and total runtime by layer. Many teams discover an inverted pyramid and had no idea.
- Define your layers explicitly. Write down what unit, integration and end-to-end mean for your team, since industry naming won't settle it for you.
- Find the misplaced tests. Any end-to-end test verifying a calculation or validation rule belongs at the base. Moving it down is usually the highest-value change available.
- Fix flakiness before adding coverage. A suite people ignore has negative value.
- Decide what the top tier is for. Name the specific journeys that must never break, and automate only those end-to-end.
- Prioritise by risk, not symmetry. Coverage percentages are a poor proxy for whether the risky parts are tested.
- Cover what automation can't reach separately. Real devices, real markets and real users are a distinct activity, not more tests in the same pipeline.
- Re-measure each quarter. Suites drift upward, because adding an end-to-end test is always the path of least resistance.
For how this sits inside a wider process, see QA testing best practices.
Covering the top of the pyramid
Most teams can build the base and the middle themselves. The tier above is where the gap sits, and not because automated end-to-end tests are hard to write. The questions at that level need real devices, real markets, and people who didn't build the product.
Global App Testing runs tests on testers' own handsets across more than 190 countries and territories, with a network of over 90,000 professional testers. That covers the device, OS and network combinations your lab doesn't hold, plus the localization and usability judgements no assertion can make. Results come back inside a release window rather than consuming one, and every defect arrives with device model, OS version, reproduction steps and evidence, landing in Jira, GitHub or Azure DevOps rather than a separate portal.
Talk to us about coverage at the top of your pyramid.
Keep learning
QA testing: process and best practices
The ultimate guide to agile testing
What is exploratory testing?
What is regression testing?
Types of software testing
FAQs
What is the testing pyramid?
A model for structuring a test suite by allocating most tests to the fastest, most granular layer and progressively fewer to the slower, broader layers. Unit tests form the base, integration tests the middle, and end-to-end tests the narrow top.
Who created the testing pyramid?
Mike Cohn introduced it in Succeeding with Agile, published in 2009. Martin Fowler and others have written extensively on its application and limits since.
What are the three layers of the testing pyramid?
Unit testing at the base, integration testing in the middle, and end-to-end testing at the top. Some versions add static analysis below the base, or a manual and exploratory tier above the top.
What is the ideal ratio between the layers?
There isn't a universal one. Ratios like 70/20/10 circulate widely but depend entirely on your architecture. A service-heavy system legitimately needs a fatter integration layer. The useful question is whether each test sits at the lowest layer that can answer its question.
What is an inverted testing pyramid?
A suite with more end-to-end tests than unit tests, sometimes called an ice cream cone. It produces slow pipelines, frequent flaky failures, and failures that are expensive to diagnose, because every problem surfaces at the layer furthest from its cause.
What is the difference between the testing pyramid and the testing trophy?
The trophy shifts weight to the integration layer, arguing that for service-heavy applications most risk lives between components rather than inside them. It responds to the same criticism that produced the honeycomb model.
Does the testing pyramid still apply with AI-generated tests?
Yes, though the economics have shifted. AI has made writing tests cheap at every layer and reduced maintenance cost at the top. It hasn't reduced execution time or diagnostic cost, and it doesn't replace human judgement about whether output is acceptable. Cheap generation also makes it easier to build a large suite that passes reliably while verifying the wrong thing.
Where does exploratory testing fit in the pyramid?
Above the automated layers. Exploratory and real-device testing address what automated end-to-end tests structurally cannot, which is whether a journey is acceptable rather than merely complete. Localization quality, usability, accessibility and real-network behaviour all sit here.
Do the layers of the testing pyramid overlap?
Some overlap is unavoidable, particularly between integration and end-to-end tests. The problem is unmanaged duplication. When two layers can answer the same question, keep the test at the lower one and remove the duplicate above it.