The Test Pyramid in Practice: Put Each Check at the Right Layer

By Xusheng Yang
5 min read

A checkout suite has fifty browser cases for discount arithmetic. It is slow to prepare and difficult to diagnose. Yet it still misses the one defect where the checkout page sends the wrong currency to the server.

The test pyramid is a way to think about a portfolio of checks with different costs and responsibilities. The practical question is which layer can reliably observe the behavior you need to protect.

This is part 14 of Testing Methods in Practice. We use a fictional discount-code feature and illustrative coverage choices, not a prescribed ratio or a report of an existing suite.

Split the feature into claims

Assume the code grants a ten-percent discount to eligible orders, is valid for a specified period, can be redeemed once per customer, and must be reflected in the final checkout total.

Separate the claims: calculation, eligibility, persistence, concurrent redemption, form behavior, and the connected customer journey. They do not all need the same environment to produce useful evidence.

Ham Vocke's Practical Test Pyramid explains a layered portfolio and its tradeoffs. Use the idea to assign responsibility rather than treating a particular numerical distribution as a requirement.

Place focused rules near the implementation

ClaimUseful layerWhy it belongs there
Percentage and currency rounding follow the agreed ruleUnitMany precise values can be checked without a browser
Invalid code shows an understandable form errorComponentLocal interaction, error, and focus behavior are observable
Customer cannot redeem the same code twiceService/integrationRequires persisted identity and redemption state
Concurrent redemptions preserve limitsIntegrationNeeds coordinated operations and real transaction behavior
Checkout sends the right order and displays the applied discountBrowser E2EConnects interface, authentication, server, and result
A broad base of rule checks supports service checks and a small set of connected browser journeys.

Open full size

Illustrative example: Assign each layer a responsibility. Keep rule variations close to the rule and use browser journeys to verify connected behavior.

Names vary between codebases. Define what a “component” or “integration” test actually includes in your environment. The dependency boundary matters more than the label.

Use the browser for connected behavior

A representative browser journey signs in as an eligible test customer, adds a known product, applies the code, checks the displayed discount, and completes a sandbox checkout. Reopen the order and verify its identity and relevant totals.

This can catch wiring mistakes: a field not submitted, a stale cart identifier, or the wrong server result displayed. It should not carry every arithmetic permutation if those rules can be checked more directly below.

Include one relevant refusal path, such as an already-redeemed code, to verify the interface connects the server's policy to useful feedback. A browser layer containing only successful journeys leaves error presentation unexamined.

Mock a boundary only when it matches the claim

Mocking a payment provider is useful when checking how your application handles a specific response. It does not establish that the provider integration is configured correctly. A separate sandbox integration check is needed for that claim.

Similarly, a component test with a mocked “discount accepted” response can verify rendering but cannot establish redemption enforcement. Keep the test name and report aligned with what ran.

Use real persistence where transaction behavior is the subject of the test. A fake repository that always returns success cannot reveal an incorrect database constraint or race between two redemptions.

Share fixtures and expectations carefully

Build a small vocabulary of known customers, orders, and codes. Avoid sharing mutable records across independent tests. The data-isolation guide explains ownership and cleanup.

Do not derive expected totals by calling the same production function under test. Independently calculate fixture expectations from the documented rule. Reusing an incorrect helper for both execution and verification can make every layer agree with the same defect.

Some overlap between layers is valuable: a browser journey can confirm the displayed result of a calculation already covered in unit tests. The duplication earns its place when it verifies integration, not merely when it repeats the same inputs through a slower route.

Improve the suite one expensive case at a time

Find a slow browser case and write down its protected claim. If the claim is a pure rule, move its variations to a focused test and retain a representative connected journey. If the claim spans identity, storage, and UI, keep it at the appropriate integration or browser level.

Do not delete broad coverage before its replacement can detect the intended failure. Compare a deliberately broken example in a disposable environment, then review the resulting evidence and diagnostic quality.

Your exercise: take one feature with at least ten test ideas. Assign each a layer and explain the observation that layer provides. Any idea with no clear observation needs a better assertion; any layer with no responsibility may be adding cost without useful evidence.

Next in the series: API and UI consistency testing.