Testing legacy code in practice

Photo by Joel BombardierPutting automated tests around legacy code is very difficult. It’s even more difficult when the team is starting to learn automated unit testing. Here there are two difficulties—trying to test code that was not meant to be tested, and the developers trying to get their head around testing concepts. You may also have a third problem—working with a language that is not conducive to modular testing. I’ve worked with at least two large teams in this particular situation.

All of that is hard work, it is time-consuming, and as time goes on the team will realise that coverage will, realistically, be even less than they imagined. And then a manager comes along and kicks them when they’re down: “Of course, by the end of the iteration you still have to deliver working software, you know.”

It’s tempting for the team to give up all hope at this point, or else say that they can’t do both.

However, the reality is not black and white. It is absolutely true that the team needs to deliver value to the business—and, yes, that value is almost certainly in the form of working software. But software and tests are not mutually exclusive, even when it’s very slow going with legacy code.

There are two parts to automated testing here. The first is extracting some code, putting a test harness around it, and writing just one test that proves it can be done in principle. This is difficult, but not necessarily time-consuming. It requires a bit of a thinking shift away from the immediate task (making a code change) to something more administrative, and setting up the first test data may require some thought. Then the second part is adding lots of test cases. This is very time-consuming, because with unmodular legacy code there are likely to be a lot of test cases. But at least the difficult work has been done in part one; this part is just about doing a lot more of it.

Knowing this should help the team balance test coverage for a piece of code against delivery. While my advice is to aim for 100% coverage—because I always want to aim for perfection—I would say that at a bare minimum they should put in a test harness with one working test. At this point, even though they’ve achieved a tiny fraction of coverage, they’ve done the hardest part. Now when someone else visits this code later it’s easy for them to add more tests.

Laying the groundwork is the hardest part, and it is the most important, but not the most time-consuming. The long-term value created for future developers is great, while the current developer can continue to deliver more immediate value in the form of actual working software.