Testing strategies: matching shapes to projects

The previous article walked through the different testing types and what each one covers. But in practice, you don't pick one type and stop there—you combine several into a coherent strategy. How you mix them depends on your project's priorities, the kind of bugs you're most worried about, and how much confidence you need from your test suite.

Several well-known analogies describe these combinations. Each one is a visual shorthand for where you should focus your testing effort, and each has strengths and weaknesses depending on the kind of software you're building.

Application size Team composition Reliance on manual testing Testing strategy
Small Developers only High Testing Ice Cone
Testing Crab
Small Developers & QA engineers High Testing Ice Cone
Testing Crab
Small Developers only Low Test Pyramid
Large Developers only High Testing Trophy
Testing Diamond
Large Developers & QA engineers High Testing Trophy
Testing Crab
Large Developers only Low Testing Trophy
Testing Honeycomb

Start with your testing goal, not the shape

Before choosing a strategy, decide what you actually want your tests to achieve. When would you consider your application "sufficiently tested"?

High test coverage is often treated as the default answer, but it isn't always the right one. Blindly chasing 100% coverage can lead to a maintenance burden without a proportional increase in confidence. The real goal is shipping software that works for your users—and your tests should reflect how those users actually interact with the system. As Kent C. Dodds puts it:

The more your tests resemble the way your software is used, the more confidence they can give you.

The closer a test gets to simulating real user behavior, the more trustworthy its results become. But that closeness usually comes with a cost, which is exactly the tradeoff the classic strategies try to capture.

The test pyramid: the classic baseline

The test automation pyramid, introduced by Mike Cohn and later expanded by Martin Fowler, is usually the first analogy developers encounter. It's drawn with three layers:

The test pyramid.

  1. Unit tests form the wide base. They're fast, isolated, and cheap to maintain, so you can afford to write many of them.
  2. Integration tests sit in the middle. They're slower than unit tests but bring you closer to how real users interact with the system. API tests and component tests typically fall into this layer.
  3. E2E tests (or UI tests) sit at the top. They simulate real user journeys through the full application stack, which makes them the slowest and most expensive to run.

The ordering reflects a deliberate tradeoff. E2E tests give you the most confidence because they exercise the whole system the way a user would, but they also consume the most resources. The pyramid pushes you to compensate by concentrating your test count in the cheap layers and using E2E tests sparingly—only for the most critical user journeys or areas most prone to defects.

Fowler highlights the two essential takeaways from Cohn's model: write tests at different levels of granularity, and write fewer tests the higher up the stack you go.

The test pyramid with arrows showing the direction of confidence and resources required for different testing types.

Adaptations for modern development

Over time, developers have pushed back on the pyramid. Its rigid three-layer structure oversimplifies testing and doesn't account for every real-world scenario. Several adaptations now exist, each shifting the emphasis based on different project needs.

The test diamond: de-emphasizing unit tests

The test diamond starts from the same three layers but shrinks the unit test tier. The reasoning: unit tests are the most prone to erosion. If you reach 100% unit test coverage, every refactor requires updating a large number of tests, which can lead to skipped or stale tests over time.

Instead of trying to cover every small unit, prioritize unit tests only for the most critical logic. The bulk of your testing effort moves to the integration layer, where tests verify that units work together correctly. E2E tests remain a thin layer at the top for the most important end-to-end user flows.

The testing honeycomb: for microservices

Spotify proposed the testing honeycomb for microservices-based architectures. In a microservice system, the most significant complexity isn't inside any single service—it's in the interactions between services. A testing strategy for such a system should shift its focus accordingly.

The testing honeycomb.

The honeycomb has three layers, mirroring the diamond but with names tailored to microservice concerns:

  • Integrated tests at the top. These depend on other systems and can pass or fail based on whether those external systems behave correctly. J. B. Rainsberger defines them as "a test that will pass or fail based on the correctness of another system." Use them sparingly for the most essential cases, since they can fail for reasons unrelated to your own code.
  • Integration tests form the bulk of the effort. These verify your service's behavior in combination with adjacent services, typically through interfaces like APIs.
  • Tests on implementation details sit at the base. They're the equivalent of unit tests, targeting isolated parts of your code that have internal complexity.

For a deeper discussion, see Martin Fowler's comparison of the pyramid and honeycomb shapes or Spotify's original engineering post.

The testing trophy: adding static analysis

Neither the pyramid nor most of its adaptations account for static analysis, which can catch typos, style issues, and certain bugs without running a single test. The testing trophy, developed by Kent C. Dodds in response to Guillermo Rauch's comment ("Write tests. Not too many. Mostly integration."), adds that layer back in.

The testing trophy.

The trophy has four tiers:

  • Static analysis is a foundation layer, catching obvious errors early at near-zero cost.
  • Unit tests still matter, but they don't dominate the way they do in the pyramid—write enough to verify critical behavior, not to blanket the codebase.
  • Integration tests are the main body. They provide the best balance of confidence and execution cost.
  • UI tests (including E2E and visual tests) sit at the top, reserved for the most critical flows.

This shape reflects a common modern recommendation: lean heavily on integration tests for most of your confidence, use static analysis as an inexpensive safety net, and reserve expensive end-to-end tests for the few paths that absolutely must work. Dodds' blog post on static, unit, integration, and E2E testing covers the reasoning in more detail.

UI-heavy strategies: from pyramid to crab

No matter which shape you pick—pyramid, honeycomb, or diamond—automated testing alone isn't the whole answer. Manual testing remains essential. The goal of automation should be to handle routine checks and free QA engineers to focus on areas that need human judgment, not to replace manual effort entirely. The question is how to combine the two effectively.

The testing ice cone

Two adaptations of the testing pyramid put greater weight on UI-focused approaches. Both offer high confidence but come at a higher cost due to slower execution. The first is the test ice cone: essentially an inverted pyramid, with the greatest emphasis on manual and UI testing and the least on unit testing. Without the manual testing layer, it's also called the testing pizza.

The testing ice cone.

The ice cone often emerges in projects where testing strategy was an afterthought. It's widely considered an anti-pattern, and rightly so—it's resource-intensive and relies heavily on manual work.

The testing crab

The test crab is similar but shifts emphasis further toward E2E and visual testing:

The testing crab.

This approach verifies not only that your application functions correctly but also that it looks right. Visual testing takes a central role here (as defined in the earlier article on testing types). Integration testing—split into component and API testing—moves further into the background, and unit testing plays an even smaller part. More details on this strategy are available in the article on the testing crab.

Despite their higher cost, both strategies have a legitimate place. In smaller projects with fewer tests needed and limited complexity to cover, a full-scale strategy centered on integration testing might be over-engineered.

Choosing a strategy: it depends

With the classic pyramid and its many adaptations on the table, the next step is picking the one that fits your product. The honest answer starts with everyone's favorite phrase: "It depends". That doesn't make it any less true.

It depends.

The right strategy—from those described here or others—depends on your application's architecture, your requirements, and above all, your users and what they need. These factors vary from project to project, and that's normal. Your primary goal is to serve your users, not to match a textbook diagram.

Real-world tests rarely separate cleanly into strict categories, and even Martin Fowler highlights the value of differing definitions—particularly when it comes to unit tests. As Justin Searls puts it:

…write expressive tests that establish clear boundaries, run quickly & reliably, and only fail for useful reasons.

Focus on tests that surface real errors your users might encounter. Don't be distracted by coverage percentages or debates over which testing type deserves what share. Tests should benefit the user, not just pad a coverage report or fuel an argument.