Is Test Driven Development (TDD) practical when working with a lot of legacy code?
Yes, it is.
There’s an opportunity to practice Test Driven Development (TDD) whenever we write new code in a legacy system. As an example, when writing a new component, service, feature, etc., which is going to be called from the legacy system.
But what if the new code is to be written right in the middle of existing legacy code?
There’s still an opportunity for TDD.
Think what the new code is supposed to do.
Maybe it’s a new if-block that checks some variables in the existing code. That if-block asks the system a question; could that question be implemented as a method, a function, or similar approach, and then called from the existing code?
Or maybe the new lines will perform some sort of task, which is an opportunity to follow TDD to design and implement the new task, and then call it from the legacy code.
What about testing the existing legacy code?
There’s a possibility that the current state of the legacy code is so poor that even changing it to call any new code is so hard that a decision is made to only change the old code, and therefore, leaving no room for TDD.
If that’s the case, there’s still value in at least trying to write characterization tests for the existing code. That’s one scenario where using code coverage can yield benefits, as opposed to the bad way many people use it.
The existing system may be massive, so it’s important to know what we should write tests for.
What do we get from that?
The experience acquired with writing tests for legacy code makes us stronger TDD practitioners; as we experience the pain of writing tests for code that wasn’t implemented and designed with testability in mind, we also apply those lessons when designing new solutions.
But what if I’m working on a greenfield project and there’s no legacy code here?
Isn’t there? Are you sure? Many developers think of legacy code as code written a long, long time ago, often in defunct languages.
Michael Feathers defined legacy code as “code without tests”.
I’ve learned to think of legacy code as “code nobody wants to deal with, with or without tests”.
Sometimes a decision is made NOT to write tests (topic for another post). We can still write the code in ways that people won’t feel compelled to run away from it, even if there are no tests yet.
Sometimes we write tests within a sprint, but we do a poor job at writing those, so tests become “code nobody wants to deal with”.
In case you haven’t already, make sure to read and apply techniques from Michael Feathers’ Working Effectively with Legacy Code.