Test Architecture: How Many of Each, and Why

Most teams know they should have unit tests, integration tests, and end-to-end tests. Fewer have a deliberate strategyfor how many of each, what each layer is allowed to assume, and what to do when the CI pipeline starts pushing past 20 minutes. The pyramid and the trophy give you that conversation in a shape β€” they aren't rules, they're defaults to start from.

πŸ€” Sound familiar?
  • CI takes 35 minutes and nobody wants to add another test
  • You have 400 E2E tests, 50 unit tests, and most prod bugs are still missed
  • Coverage is 92% but refactors keep breaking unrelated tests
  • Flaky tests are retried automatically and the retry rate is climbing

The architecture decisions you make about your test suite β€” distribution, isolation, parallelism, what to gate on β€” drive engineering velocity more than any specific testing framework choice.

The Pyramid (Cohn)


flowchart TB
    E[E2E - few, slow, broad coverage]
    I[Integration - some, medium, real adapters]
    U[Unit - many, fast, narrow]
    E --> I --> U
    style E fill:#fdd
    style I fill:#fed
    style U fill:#dfd
      

Many fast unit tests at the bottom; fewer integration tests in the middle; a small set of E2E tests covering the most important user journeys at the top. Each level is roughly 10Γ— the cost of the level below it.

The Trophy (Dodds)

Kent C. Dodds proposed a different shape for frontend-heavy apps: a small base of static analysis (TypeScript, ESLint), some unit tests, a large integration body (component tests with React Testing Library), and a thin layer of E2E. The idea: integration tests give the best confidence-per-second for UI work, and TypeScript catches an entire class of bugs that would otherwise need unit tests.


flowchart TB
    E2[E2E]
    INT[Integration - the largest layer]
    UN[Unit]
    SA[Static analysis]
    E2 --> INT --> UN --> SA
    style INT fill:#dfd
      

Which Shape Fits Which Project

Project typeLikely shape
Domain-heavy backend (banking, billing, logistics)Pyramid; pure-domain unit tests are gold
SPA / frontendTrophy; component integration tests dominate
MicroservicesPyramid + heavy contract testing
Glue / orchestration codeMostly integration; less unit value
Data pipelinesPyramid + golden-output snapshot tests

Coverage Thresholds That Don't Lie

Line coverage above ~80% has rapidly diminishing returns and starts incentivizing weak tests written for coverage's sake. A working policy:

  • Critical paths (auth, payments, data integrity): high branch coverage plus mutation testing.
  • Regular code: 70–80% line coverage is fine if assertions are meaningful.
  • Glue / configuration / dead-simple code: don't enforce a number; review and move on.

Mutation testing (Stryker for JS/.NET, Pitest for Java) is the real coverage signal: it mutates code (changes> to >=, removes lines) and runs your tests. Mutants that survive expose weak assertions. Aim for high mutation score on critical paths.

Test Pyramid Math β€” A Pipeline Budget

Assume you have a 10-minute CI budget. Roughly:

  • 5,000 unit tests Γ— ~5 ms each = 25 s. Parallelized across 4 workers, ~10 s.
  • 500 integration tests Γ— ~200 ms each = 100 s. Parallelized, ~30 s.
  • 30 E2E tests Γ— ~5 s each = 150 s. Sharded across 4 browsers / workers, ~50 s.
  • Build + dependency install + framework startup: ~3 minutes.

That's a 5-minute pipeline. The moment you have 300 E2E tests instead of 30, you're at 18 minutes. Treat the E2E count as the most expensive constraint in the suite.

Parallelism and Isolation


flowchart LR
    PR[PR commit] --> CI[CI orchestrator]
    CI --> W1[Worker 1: unit + lint]
    CI --> W2[Worker 2: integration shard A]
    CI --> W3[Worker 3: integration shard B]
    CI --> W4[Worker 4: e2e shard A]
    CI --> W5[Worker 5: e2e shard B]
    W1 & W2 & W3 & W4 & W5 --> GATE[Merge gate]
      

Run independent layers in parallel. Within a layer, shard by test file or test ID. Each worker needs its own ephemeral resources β€” DB, cache, fixture data β€” to avoid cross-test interference.

Quality Gates

Decide what blocks the merge button:

LayerBlock on?
TypeScript / lint / formatYes, always
Unit testsYes
Integration testsYes
Contract tests + can-i-deployYes (between services)
E2E smoke (3–10 tests)Yes
E2E full suiteOn main or nightly, with auto-revert
Visual regressionSoft signal; reviewed in PR
Performance benchmarksSoft signal; alert on regressions

Reducing Flake

  1. Quantify it. Track first-attempt pass rate per test. Quarantine tests below 95%.
  2. Eliminate sleeps; use proper waits and fake timers.
  3. Per-test isolation β€” fresh DB schema or aggressive truncation, fresh browser context.
  4. Pin dependencies and toolchain versions; container the runtime.
  5. One retry on CI for known cosmic-ray flakes; never retry locally β€” fix it.

CI Pipeline Optimizations

  • Run cheapest gates first (lint, types) so PRs fail fast.
  • Cache the package install across runs; restore the browser binaries Playwright needs.
  • Detect affected modules (Nx affected, Turborepo, change detection) and only run downstream tests.
  • Reuse a Docker layer cache; build images once, push by tag, run tests against the artifact.
  • Move slow visual regression to a separate workflow that runs on a label or schedule.

The Health Dashboard

A test suite is infrastructure. Treat it like one:

  • P50/P95 pipeline duration.
  • Flake rate per test, per suite.
  • Coverage trend over time (per service / module).
  • Top 10 slowest tests β€” split or optimize them.
  • Suite owners β€” every quarantined test has a person, every old quarantine has a deadline.

Pitfalls

Inverting the pyramid

Hundreds of E2E tests β€œbecause they cover everything” results in a 40-minute, 5%-flake suite that no one trusts. Push tests down the pyramid: most of what an E2E catches can be caught by integration tests at one-tenth the cost.

Coverage as a target

Goodhart's law. The moment a coverage number is a target, people write tests that touch lines without asserting anything. Use coverage as a debugging tool, not a KPI.

No owner for the pipeline

If β€œCI is slow” is everyone's problem, it's no one's. Name a tooling/infra owner with an explicit SLO on pipeline duration and flake rate. It's a real role and a high-leverage one.