Starting from zero is more common than you think
If your project has no automated tests, you're not alone — and you're not doomed. A large share of the codebases we inherit or audit have little or no coverage. The teams aren't careless: tests simply never became a habit, and the longer it goes, the harder it feels to start.
The good news: you don't need 80% coverage to get value. You need the right tests in the right places, and a way to make the habit stick.
What not to do
The most common failure mode is the "big testing push": someone decides the team will spend a sprint (or three) writing tests for everything. It never works. The code wasn't written to be testable, the tests that do get written are brittle, morale drops, and the initiative dies — confirming everyone's belief that "tests don't work here."
Second most common failure: chasing coverage as a number. Coverage measures which lines were executed, not which behaviors are protected. 90% coverage can still let a critical bug through if the tests assert nothing meaningful.
Where to actually start
Start where the risk is. Ask two questions about every part of the system: how bad is it if this breaks, and how likely is it to change. The intersection — critical and frequently touched — is where your first tests go. That's usually business rules: pricing, invoicing, permissions, state transitions.
Concretely, the first valuable tests are almost always:
- Unit tests around pure domain logic — the calculations and decisions that don't touch the database or HTTP. They're fast, stable, and they document how the business works.
- A handful of end-to-end tests on the critical path — sign up, pay, deliver. Few, slow, and worth every second. If only five tests survive a disaster, let them be these.
Skip, for now: testing getters and setters, testing the framework, and testing code that's about to be deleted.
Make the code testable as you touch it
Legacy code resists tests because everything is tangled: the business rule lives inside the controller, which talks to the database directly. Don't untangle it up front. Instead, apply the scout rule: every time you touch a piece of code for a feature or a fix, leave it slightly more testable — extract the decision into a function, inject the dependency, and cover that function before you move on.
In six months, the code that changes often — which is the code that matters — will be covered, and you never stopped delivering features to get there.
The regression-test habit
The single highest-value testing practice costs almost nothing: every bug gets a test. When something breaks in production, write the test that would have caught it, watch it fail, then fix the code. Bugs rarely happen twice in exactly the same place, but they cluster in the same fragile areas — and those areas accumulate armor over time.
This also builds the team's trust in the suite, which is the real bottleneck. A suite that has caught real bugs gets maintained; a suite that only exists for a metric gets ignored.
Tests that people hate get deleted
A test suite survives only if it's an asset, not a tax. The properties that make it one:
- Fast: the unit suite runs in seconds. If it takes minutes, people stop running it locally and the feedback loop dies.
- Deterministic: no random failures, no dependence on execution order or the time of day. One flaky test, tolerated, teaches everyone to ignore red builds — and then the suite is decoration.
- Readable: a test is executable documentation. If a newcomer can't tell what's being verified by reading it, it's not doing half its job.
Delete or quarantine flaky tests immediately. It feels wrong; it's essential.
Where CI fits in
Tests that don't run automatically don't exist. Wire the suite into your pipeline so every push runs it, and make failures visible and blocking — starting with the fast tests, leaving the slow end-to-end ones for a later stage if needed. The pipeline doesn't need to be sophisticated: it needs to be non-negotiable.
A realistic 90-day starting point
- Weeks 1-2: get the infrastructure running (framework, CI job, one trivial test). The first green build matters more than any single test.
- Weeks 3-6: cover the two or three most critical business rules with unit tests; add the bug-regression habit for every production incident.
- Weeks 7-12: add end-to-end tests for the critical path; apply the scout rule on every new change.
At the end of 90 days you won't have impressive coverage. You'll have something better: a suite that catches real problems, a team that trusts it, and a habit that compounds.
Want help getting there faster?
Setting up a testing strategy on a legacy codebase is one of the things we do most often: assessing where the risk is, making the first modules testable, and coaching the team until the habit sticks. If your project is flying without a safety net, let's talk — the first tests are closer than they seem.