← Study Notes Programming Fundamentals


Programming Fundamentals

Testing

Automated checks that pin behaviour so you can change code without fear — unit tests for logic, integration tests for wiring, end-to-end tests for whole flows (the testing pyramid). They document intent and catch regressions in CI. The art is testing observable behaviour, not implementation details, so tests survive a refactor.


Purpose

Automated tests pin a system's behaviour so it can be changed without fear: unit tests exercise logic in isolation, integration tests verify components wired together, and end-to-end tests drive whole user flows. They also serve as executable documentation of what the code is supposed to do.

When to Use It

Unit tests fit pure logic — pricing rules, parsers, validators; integration tests fit the seams — API routes hitting a real test database; a few end-to-end tests cover the flows that must never break: signup, checkout, login. Run in CI, they gate every change (the classic pyramid: many unit, fewer integration, fewest E2E).

Trade-offs

Tests cost time to write and maintain, and testing implementation details makes refactoring painful — the suite breaks even when behaviour did not. Flaky end-to-end tests are the worst failure mode: once a team starts ignoring red builds, the suite's entire value evaporates.

Implementation

Test observable behaviour through public interfaces: given inputs, assert outputs and effects. Keep unit tests fast and deterministic; use test doubles at external boundaries (network, clock, randomness) and real components where cheap (an in-memory or containerised database). Make tests part of CI so a red build blocks the merge.