skip to content

How would you split HTTP-layer coverage across handler-only, slice and full-stack tests for a service with hundreds of routes?

level: principalimportance: nice to knowfreq 38%

answer

  1. cost shape decides placement
  2. cheapest tier that can see the fault
  3. slices cost per configuration
  4. full stack proves the graph, coarsely
  5. steer by classified escapes

basics

~20 s

Push case volume down to handler calls, which cost nothing; assert each route's contract once in a slice, whose cost is per configuration; spend the full stack on proving the real graph builds. Steer by which faults escape.

solid answer

~50 s

Start from the cost shape of each tier. A direct handler call has no fixed cost and scales with cases, so branch and validation combinatorics belong there. A slice pays a framework start-up per configuration and amortises it over every test sharing it, so it should carry one contract assertion per route — status, headers, payload shape — with as few distinct configurations as the service allows. The full stack pays a real boot and real resources, so it gets breadth only where wiring genuinely differs, plus one start-up proof for the whole application. Then make the split empirical: track which class of defect actually reaches production. Escaping binding or serialisation faults means the slice tier is too thin; escaping construction or configuration faults means the full-stack tier is. A single-tier suite fails either way.

go deeper

for a junior

Take away the placement rule: put each case in the cheapest tier that can actually see the fault, and remember that the cheap tier cannot see routing, binding or serialisation at all.

for a middle

Be able to state each tier's cost shape — none, per configuration, per boot — and match it to the fault classes that tier uniquely reveals.

for a senior

Show the operating loop: classify escaped defects by the tier that could have caught them, and grow that tier rather than adding tests where they are easiest to write.

for a principal

Own the tradeoff end to end, including the organisational half: the split survives only if the cheapest tier is the easiest to write, and the region left to production must be named rather than assumed closed.

## Start from the cost shape, not from a ratio The three tiers do not differ only in speed; they differ in **how their cost scales**, and that is what decides where each kind of case belongs. | Tier | Fixed cost | Scales with | Evidence it uniquely produces | |---|---|---|---| | Direct handler call | None | Number of cases | Branch and error behaviour inside the body | | Router-and-hooks slice | One framework start per configuration | Number of distinct configurations | Route matching, binding, hooks, status, headers, serialisation | | Full stack on real wiring | A real boot, plus real resources | Number of environments and wiring variants | The deployed graph builds, configures and answers | A ratio like "seventy, twenty, ten" is a symptom of a good split, never a rule that produces one. What produces one is asking, per case, **which tier is the cheapest place this fault can be seen**. ## A workable split at hundreds of routes 1. **Volume goes to the bottom.** Every validation branch, every domain error, every awkward edge in the handler body is a direct call. At this scale that is thousands of cases costing seconds in total. 2. **One contract assertion per route in a slice.** For each route, a single test proves it matches under the right method, binds its inputs, and answers with the agreed status, the headers clients act on, and a payload of the promised shape. Hundreds of such tests on a handful of shared configurations remain cheap, because the start-up is amortised. 3. **Configuration discipline is the slice budget.** Treat a new slice configuration as an expensive purchase; consolidate aggressively, because suite runtime tracks configuration count far more than test count. 4. **The full stack is narrow and deliberate.** One proof that the application starts on real wiring, plus one real request per route family whose wiring is genuinely distinct. Assert coarsely there — status and a minimal shape — and leave detail to the slice. 5. **True externals stay replaced even at the top.** The point of the full stack is your graph, not someone else's availability. ## Steer by what escapes, not by a target The honest feedback signal is the defect that reached production, classified by which tier could have caught it: - Escaping **logic** faults mean the bottom tier is thin or its cases are shallow. - Escaping **binding, status, header or serialisation** faults mean routes lack a slice-level contract test, or those tests assert the wrong thing. - Escaping **construction, registration or configuration** faults mean the full-stack tier is too narrow. - Faults escaping in more than one class at once usually mean the suite is concentrated in a single tier and everything outside it is untested by construction. This classification is worth keeping as a lightweight running list. It converts an argument about testing philosophy into an argument about observed escapes, which is far easier to settle. ## The failure modes of a single-tier suite - **All handler calls.** Fast, green, and unable to prove any endpoint exists. Contract drift ships freely. - **All slices.** The HTTP surface is well covered, but nothing ever builds the real graph, so every deployment carries construction risk. - **All full stack.** Every fault is catchable in principle, but the suite is slow, the feedback loop collapses, flakiness arrives with real resources, and people start skipping it — which removes more coverage than any deliberate choice would have. ## The organisational half of the answer A split only holds if the cheapest tier is also the **easiest to write**. If adding a direct handler call requires assembling an elaborate imitation of a request, engineers will write a slice test instead, and the suite drifts upward one commit at a time until it is slow. So the leverage is in the code shape: handlers that take plain values and return plain results, a small number of shared slice configurations, and one obvious place to add a full-stack case. Where that shape is not available — because handlers are written against framework-owned request and response objects — accept it, put the volume in the slice, and spend the saved argument on keeping configurations few. Finally, state the boundary you are buying. At any split there is a region only production exercises; naming it, rather than pretending it is closed, is what makes the plan defensible when something escapes anyway.

  • Why is a fixed tier ratio a poor way to plan a suite?
    A ratio describes the outcome of good per-case decisions, not a rule that produces them. Services differ in how much behaviour lives in declarations versus code, so the same ratio can be right for one and badly wrong for another. Decide per case by the cheapest tier that can see the fault, then observe what the ratio turned out to be.
  • What makes a suite drift upward into slower tiers over time?
    Friction at the bottom. If calling a handler directly means building an elaborate imitation of a request, engineers reach for the routed test instead, and every such choice is permanent. Keeping handlers thin and their inputs plain is therefore a test-performance decision as much as a design one.
  • How do you argue for more full-stack breadth without slowing the suite unacceptably?
    Tie it to escapes. If construction or configuration faults have reached production, add one coarse real-wiring request per distinct wiring variant, not per route, and keep the assertions minimal. Breadth at that tier should grow in units of wiring variants, which are few, rather than in units of cases, which are many.

saying these in an interview costs you the question

  • Names a fixed tier ratio as the plan itself
  • Puts branch combinatorics in the slowest tier available
  • Adds a slice configuration per test, then blames start-up cost
  • Treats the full stack as a coverage tier rather than a wiring proof
  • Claims a single tier can cover every fault class economically