Retrofitting tests into legacy code is difficult process but can give us the ability to redesign and improve the quality of that software as we go. It is generally a good idea to add tests before refactoring code. Tests serve as a safety net so if I make a mistake in refactoring the test will tell me.
Sometimes we need to refactor code in order to add tests. These are generally simple refactoring or what Michael Feather’s calls “safe refactorings” in his book Working Effectively with Legacy code. Several IDE’s provide facilities for automating safe refactorings. Once tests are added you can do more complex refactoring.
It is always a good idea to make a test fail before making it succeed. How do we do this when we are retrofitting tests into working code? The answer is to temporarily change the working code. For example, if I am writing a test for code that sums 1 and 2 then I’ll hardcode a return value of 1 to make it fail, then I’ll watch it fail and then I’ll back out my change using Control-z and watch the test succeed. This may seem like a pointless activity but you may be surprised at the times you expect a test to fail and it succeeded because of something you weren’t expecting.
If we are going to take the time to clean up legacy code then we must do it in a way that we don’t end up with the same problem years from now. If we want our software to survive and continue to be used and changed then we simply have to start building it with some core engineering practices that support change, including unit tests.