Automation Testing: What It Is For and Where You Actually Need It
A practical guide to test automation — the testing pyramid, which tests earn their maintenance cost and which do not, and how to decide what actually deserves automated coverage.
Automated testing is not a binary "have it or don't" — it is a portfolio of different test types, each with a different cost, speed, and failure mode. Teams that automate the wrong things end up with a slow, flaky suite that gets ignored; teams that automate the right things catch real regressions before a user does. This is how to tell the difference.
What automated testing is actually for
- Catching regressions — confirming a change did not break something that used to work.
- Making refactoring safe — the confidence to change implementation without fear of silent breakage.
- Documenting expected behavior — a well-named test is executable documentation that cannot go stale silently.
- Reducing manual QA load on the parts of the app that change rarely but must never break.
The testing pyramid
| Layer | What it tests | Speed | Typical share |
|---|---|---|---|
| Unit tests | A single function/component in isolation | Milliseconds | Majority |
| Integration tests | Several units working together | Seconds | A meaningful minority |
| End-to-end (E2E) tests | Real user flows through the actual app | Seconds to minutes | A small, deliberate set |
The pyramid shape matters because it is also a cost curve: unit tests are cheap to write and run, and E2E tests are expensive, slow and more fragile — small changes to unrelated UI can break them. A suite shaped like an inverted pyramid (mostly E2E, few unit tests) is usually a symptom of testing added late, after the code was already hard to unit-test.
Where automated tests earn their cost
- Business logic with real branching — pricing calculations, permission checks, anything with edge cases a human reviewer will eventually miss.
- Code that changes often and is depended on by many other parts of the app — the blast radius of breaking it is large.
- Critical user flows — checkout, sign-up, payment — where a regression has a direct, quantifiable cost.
- Anything that has broken in production before — a regression test for a real past bug is close to free insurance.
Where it usually is not worth it
- Pure UI styling and layout — visual regression tools fit this better than functional E2E assertions.
- Code that changes weekly and has no meaningful logic — the test maintenance cost exceeds the value caught.
- Exhaustive E2E coverage of every permutation — a handful of critical paths in E2E, with the combinatorics pushed down into unit/integration tests, is both cheaper and more reliable.
- Testing a third-party library's own behavior instead of your integration with it.
Where automation fits in the process
Automated tests earn their keep in CI, running on every pull request, catching regressions before review rather than after deploy. A green suite that nobody trusts (because it is flaky) is worse than no suite — flaky tests need to be fixed or deleted immediately, not left to erode trust in the whole safety net.
The bottom line
The right amount of automated testing is not "100% coverage" — it is coverage weighted toward business-critical logic and code with a large blast radius, kept fast and reliable enough that the team actually trusts and runs it. This same judgment — where to spend engineering effort for the highest return — is what we apply across every engineering engagement, including legacy migrations where test coverage is often the single biggest de-risking factor.
Frequently asked questions
What percentage of code should be covered by automated tests?
There is no universal target — coverage percentage is a weak proxy for actual risk reduction. A codebase with 60% coverage concentrated on business logic and critical flows is safer than one with 95% coverage padded by trivial getter/setter tests. Aim coverage at risk, not at a number.
Should we write E2E tests for every user flow?
No — E2E tests are the most expensive and fragile layer, so they should cover a small, deliberate set of critical flows (checkout, sign-up, core actions) rather than every possible path. Push the combinatorics of edge cases down into unit and integration tests, which are faster and more stable.
How do we deal with flaky tests?
Fix or delete them immediately rather than let them linger — a flaky test that gets re-run until it passes trains the team to ignore failures, which quietly destroys trust in the entire suite. If a test cannot be made reliable quickly, it is often better to remove it and rely on a different layer than to keep a known-unreliable check in CI.