Step 1
Test pyramid · trade-offs
20 min
Test pyramid · trade-offs
"Just write more tests" does not help. Where and how much to invest is the real design.
1. The pyramid
/ E2E \ 5%
/--------\
/ integ \ 15%
/------------\
/ unit \ 80%
/----------------\
Broad base, narrow top. Most tests are unit; E2E is minimal.
2. Traits per layer
| Layer | Example | Speed | Confidence | Fragility |
|---|---|---|---|---|
| Unit | cn(), formatDate, pure fns |
<1ms | low | low |
| Integration | DB + queries · API routes | 100ms–1s | high | medium |
| E2E | browser automation | 1–30s | very high | high |
E2E tests get flaky from network and timing.
3. When which layer?
- Pure input → output → unit (vitest, pytest)
- DB or external APIs → integration (testcontainers)
- User flows (login → purchase) → E2E (Playwright)
Wrapping pure functions with E2E wastes CI time.
4. Coverage % is not the goal
80% coverage ≠ 80% confidence. What matters is scenario coverage.
- login success
- login failure (wrong password)
- session expired
- rate limit exceeded
Four scenarios make login.ts solid. Not lines.
5. "Tests are too slow"
- Unit in watch mode
- Integration in CI only
- E2E once per PR before merge
Running E2E locally every time kills productivity.
6. Handling flaky tests
- 1 fail → warn, 3 fails → block
- Can't reproduce →
test.skip()+ a 2-week deadline, not a forever lock - Root cause: timing, network, shared state
7. Tests are part of docs
describe("logAdminAction", () => {
it("rejects reason under 30 chars", ...);
it("fire-and-forget — main request still 200 on failure", ...);
it("fills user_id via cookies fallback when no request", ...);
});
describe / it names are specification.
Closing
The pyramid is a balance, not a rule. For an MVP, a couple of E2E tests plus a few unit tests is fine. For payments or healthcare, tilt toward more E2E. Balance to the cost of incidents.
Next
- 02-vitest-mock-patterns