Bringing automated testing into a codebase that has run for years without it feels like joining a marathon at the halfway mark. There is no clean starting point, no brand new module to show off good habits in isolation, and every test has to live alongside code that was never built with testing in mind. None of that was a reason for us to skip it. It was a reason to think carefully about where to actually begin.
The natural urge is to write tests for small, simple, self contained functions first, since they are easy. That builds a test suite quickly, but it does not actually lower the actual risk, because the code that worries an engineering manager the most, the part nobody wants to touch right before a release, is almost always the complicated, stateful, business critical logic, not the small utility functions. We chose to start there instead, writing tests that simply describe how that scary code behaves today, bugs included, so that any future change to it is measured against something actual, instead of against nothing at all.
In an old codebase, the hard part of testing is rarely writing the actual check. It is getting the code into a state where it can even run at all, a database connection, a session, a set of global values the code simply assumes already exist. A shared, well kept setup script that builds this environment once turned out to be the single most valuable thing we built for this whole effort, since every test written after that benefits from it, instead of solving the same setup problem all over again.
A coverage percentage that only counts whether a line of code ran, without checking whether anything meaningful was actually proven, gives a false sense of safety. A test suite with a high percentage but very few actual checks has only proven the code runs, not that it works correctly. We now look at how much is actually being proven, and review test quality from time to time, not only the number on a dashboard.
The moment "write tests" becomes a separate item from "ship the feature," it gets pushed behind the next feature forever. Tests that go out in the same change as the feature they cover, reviewed together by the same people, are far more likely to actually still exist six months later than tests that were promised for "next sprint."
The actual benefit of an automated test suite in an old codebase was never catching every single bug. It was the change in how the team ships work. A team with a test suite they actually trust releases more often, with less fear, because finding out something broke moved from a customer reporting it, to a build failing in three minutes.
Maybeach Tech helps engineering teams build lasting automated testing habits into codebases that ran for years without them. Get in touch and let us talk about where to start.