What the pyramid looks like
- Base: unit tests: many, fast, stable; cover functions and modules;
- Middle: integration: verify modules working together, DB and APIs;
- Top: end-to-end: few but vital; cover key user paths.
Upside-down is painful
A pile of e2e tests is slow, flakes on timing or animation, and hard to localize. Push most assertions down to unit tests for faster feedback.
Two reminders
- Don't chase 100% coverage: prioritize critical logic and edges; glue code matters less;
- Tests must be maintainable: flaky tests are worse than none; assert behavior, not implementation details.
Test data and environment hygiene
Good assertions cannot save you if data and environments are chaotic:
- Each test builds its own data: use factories or fixtures rather than a shared seed database, or tests interfere and cannot run in parallel;
- Isolation strategy: build unit-test data in memory and give integration tests their own schema or instance, never a shared dev database;
- Inject the clock: make "now" an injectable dependency rather than a direct system call, or tests fail cyclically across month ends and DST;
- Clean-up and idempotency: tests should remove what they create and be rerunnable, so a mid-run failure does not poison the next attempt;
- Consistent environments: keep dependency versions and config templates aligned across local, CI and staging, drift is the top cause of "works locally, fails in CI".
Tests need observability too: a failure should print the input, expectation, actual result and request ID, not merely "assertion failed".
Making tests trusted
- Failures must be explainable: a failing test should immediately show whether the expectation, the environment or the code is wrong. "Assertion failed" alone trains people to ignore the signal.
- Delete permanently failing tests: long-skipped or known-failing cases teach the team to disregard results. Fix or remove them, recording the reason in your tracker.
- Control runtime: unit tests should finish in seconds; slower means heavy dependencies and they get pushed out of daily use until a pre-release run.
- No ordering dependence: tests should run individually and in parallel, order dependence makes failures hard to reproduce and blocks incremental runs.
- Tie regressions to defects: add a reproducing case for every bug fixed; over time the suite becomes a precise list of known issues.
The value of a suite equals whether the team will act on its results. Everything that improves readability, speed and stability serves that one condition.