Testing Strategy & Test Pyramid (Beginner)
Learn how to choose the right tests: unit, integration, end-to-end—plus reliability and speed tradeoffs.
Theory
A good testing strategy optimizes for:
- confidence (catch bugs before release),
- speed (fast feedback),
- cost (time to maintain tests),
- debuggability (tests tell you what broke).
The Test Pyramid (practical guideline)
- Unit tests: fast, isolate logic, run often
- Integration tests: verify interactions (DB, queues, APIs)
- End-to-end (E2E): verify critical paths (UI/API flows)
Rule of thumb:
- many unit tests
- fewer integration tests
- very few E2E tests
What “good” unit tests look like
- Test one behavior per case
- Avoid heavy setup/IO
- Use deterministic inputs
- Name tests by behavior:
should_return_total_when_items_added
Keep integration tests stable
- Use test containers / ephemeral DBs where possible
- Seed deterministic data
- Control time (clock abstraction)
- Avoid external network dependencies
Code Example (JS/TS: unit test with deterministic clock)
// price.ts
export function applyDiscount(subtotal: number, percent: number) {
return subtotal * (1 - percent / 100);
}
// price.test.ts
import { applyDiscount } from "./price";
test("should apply percent discount correctly", () => {
expect(applyDiscount(200, 10)).toBe(180);
});
Practice
- Pick a small feature (e.g., “create user profile”).
- Write:
- 5 unit test cases for pure logic
- 2 integration tests for DB boundary (CRUD)
- 1 E2E test for the critical flow
- Identify which failures are likely to be “fast” vs “slow” and adjust your pyramid.
Common pitfalls
- Only writing E2E tests (slow + flaky)
- Snapshot tests without meaningful assertions
- Tests that depend on time/randomness without control
- Brittle mocks that break during refactors
Frequently Asked Questions
Should I always aim for 80% unit tests?
Not blindly. Aim for the pyramid that matches your risk: more unit tests for logic, more integration tests for boundaries, and fewer E2E tests for critical user flows.
Why do end-to-end tests fail frequently?
They touch more systems (network, time, external dependencies). Keep them small, stable, and mock what you can.