Writing tests for legacy code (Irrational Software)
Yesterday, I read an article by Michael Feathers titled Working Effectively With Legacy Code (PDF). It presents a way to, in XP, set up programmer tests for code that didn’t have any before. This might not sound like a big deal, but the nature of tests in XP differs from that of “traditional” tests.
The foremost reason for this difference is that, in XP, tests are written before the code. The technique is to write a unit test for a small fraction of the code that is to be implemented. At this stage, you think about the interface for the code to be written. Then you switch to writing code until the test passes compilation and execution. At this stage, you are thinking about passing the failing test – that is, to provide the small piece of functionality the test expects.
This technique renders code that is testable. For code that was written with the intention to implement some functionality, testability is often not part of the game. Testing is something that comes after implementing, if at all. And when it’s time to test, the easiest way to do so isn’t by setting up programmatic tests, but to drive the entire app and pay attention to any strange things that occur.
The article describes a procedure for bringing “untested” code under test, via what Michael Feathers calls “test coverings”, which is the same as using the tests as scaffolding. The idea is to set up tests that preserve the behavior of the code, regardless of whether that behavior is correct or not. Doing so will indead uncover bugs in the code, but fixing them is something to be done separately.
In the beginning of the article, Michael Feathers describes the difference between legacy code and non-legacy code as a matter of having tests or not. Legacy code lacks tests while non-legacy doesn’t. In the next sentence, he asks, “how easy would it be to modify your code base … if it could tell you when you made a mistake”. This is what the difference between legacy and non-legacy code is about: modifiability – which is something tests can enable.
But the point that legacy code lacks tests is effective when discussing this. When thinking about bringing legacy code under test, I have found it helpful to think that all software is tested, which is similar in regard that it is in some sense true, but that it helps the discussion. (I will get back to this later, I think.) I recommend reading this article; it is very good and contains a lot of things I hadn’t thought about previously. Do read it if you want to start doing test-driven development, but have a whole lot of code that lacks tests!