The day I started believing in unit tests
Engineers debate when unit tests truly pay off, contrasting fast, isolated checks of small “units” of code with slower but more realistic integration and end‑to‑end tests. Many praise unit tests for catching subtle logic bugs, supporting refactoring and documenting intended behavior, but criticize brittle, over‑mocked tests that calcify implementations and rarely find real defects. A recurring theme is trade‑offs: test speed vs realism, test maintenance vs code flexibility, and how much testing is justified by the risk, domain complexity, and quality of the team’s engineering practices.
Definitions and Scope of “Unit Tests”
- Strong disagreement on what “unit test” means.
- One camp cites the classic definition: very fast tests that avoid DB, filesystem, network, and special environment setup.
- Others define by responsibility:
- Unit: single responsibility, isolated logic.
- Integration: vertical slice with relevant dependencies.
- End-to-end: complete user journey.
- Some argue the labels are effectively meaningless in practice; “unit” and “integration” tests often look the same.
- Another view: unit = test that runs independently of other tests (no shared-state interference).
Perceived Benefits of Unit Tests
- Fast feedback loop; can be run constantly while coding.
- Catch subtle edge cases and regressions, especially in complex logic, pure functions, or dynamic languages.
- Encourage better design: smaller, decoupled modules, explicit dependencies, fewer side effects.
- Act as executable documentation and specifications, and as guardrails during refactoring.
- Useful for encoding known bugs and edge conditions as permanent checks.
- Some report major “aha” moments when writing tests reveals non-obvious bugs.
Critiques and Common Failure Modes
- Many corporate tests are tightly bound to implementation and mocks, making refactoring painful and brittle.
- High coverage can mask low value: tests often “verify yesterday’s behavior” or just re-implement the code.
- Strict coverage targets encourage meaningless tests and gaming metrics.
- Large suites can become slow (tens of minutes), discouraging developers from running them.
- Poorly maintained tests calcify bad designs, get deleted when they block “velocity,” and erode trust.
Integration, E2E, and Realism vs Speed
- Debate over the “testing pyramid”: some argue integration/E2E tests deliver more real-world value and catch UI, layout, configuration, and cross-service issues that unit tests never will.
- Others stress integration/E2E tests’ higher cost, flakiness, and maintenance burden; recommend keeping them fewer and more targeted.
- Tools like containers and in-memory adapters blur the line: integration tests can still be relatively fast.
- Several commenters favor testing “units of coherent functionality” (often multi-class slices) rather than single classes.
Other Techniques and Meta-Points
- Static typing reduces some bug classes but doesn’t replace tests; discussion on how expressive types vs tests trade off.
- Property-based testing highlighted as powerful for discovering unexpected edge cases and spec inconsistencies.
- Tests are one layer among code review, manual QA, and, in safety-critical systems, heavy human verification and formal processes.