Tests exist to make change safe, not to hit a coverage number. Write the tests that would actually catch the bug you're worried about.
The pyramid, roughly
- Favor many fast unit tests for pure logic (validation, calculations, transformations), fewer integration tests for how pieces connect (API route + database, component + store), and a small number of end-to-end tests for the critical user paths (signup, checkout, the core action of the product).
- Never invert the pyramid (mostly slow E2E tests, few unit tests) — it works at first and becomes unbearably slow and flaky as the suite grows, and failures stop pointing at a specific cause.
What to actually test
- Test behavior and outputs, not implementation details — a test that breaks every time you refactor internals without changing behavior is testing the wrong thing, and will be disabled or deleted under deadline pressure, defeating its purpose.
- Prioritize: business logic with real consequences (pricing, permissions, data integrity) first, edge cases and boundary values second (empty input, max length, zero, negative numbers, exactly-at-the-limit), then the happy path — the happy path is usually what manual testing already covers; edge cases are what slip through.
- Every bug fix gets a regression test reproducing the bug before the fix, so it can never silently reappear.
Test isolation
- Each test must be able to run alone and in any order — shared mutable state between tests (a global counter, a database row left over from a previous test) produces flaky, order-dependent failures that waste hours to diagnose.
- Reset or recreate any external state (database, mocked time, mocked network) between tests; never rely on test execution order for correctness.
Mocking discipline
- Mock at the boundary of your system (network calls, time, randomness, external APIs) — never mock the code you're actually trying to test, or the test proves nothing.
- Prefer a real (local/test) database over mocking the database layer for anything involving actual queries — a mocked query can drift from what the real database actually enforces (constraints, cascades, types).
CI requirements
- The full test suite must run on every pull request before merge, and a red suite must block merge — a red suite that's "usually fine to merge anyway" trains everyone to ignore it, and then it stops catching anything.
- Keep the suite fast enough that people actually run it locally before pushing — a test suite so slow nobody runs it locally is a test suite that only catches problems after they're already in CI.
Flaky tests
- A flaky test (passes sometimes, fails sometimes, same code) must be fixed or quarantined immediately, never ignored — a known-flaky test people learn to re-run until green is a test that has stopped testing anything.
Comments (0)
Sign in to comment. Sign in
No comments yet. Be the first to add one.