A colleague of mine sent me an article on ArsTechnica that was a short discussion on whether it is a good idea to write tests for legacy code. This was made up from a collection of posts on Stack Exchange. The original question was as follows:
“Suppose one had a relatively large program (say 900k SLOC in C#), all commented/documented thoroughly, well organized and working well. The entire code base was written by a single senior developer who no longer with the company. All the code is testable as is and IoC is used throughout—except for some strange reason they did not write any unit tests. Now, your company wants to branch the code and wants unit tests added to detect when changes break the core functionality.
Is adding tests a good idea? If so, how would one even start on something like this? “
Original question posted by Paul over at Stack Exchange
It’s a good question, so I thought I would write down my thoughts on it. I am a firm believer in Test Driven Development (TDD) and this is much easier when you are working on a nice new green field project (writing a new system). Unfortunately we don’t always have the luxury of working on new systems and we have to maintain older legacy systems, or brown field applications. If you have a large brown field system, I personally do not think there is much value in getting your team of developers to sit there and wrap the whole system in tests. Whilst it may feel nice to know the application is covered in tests, the level of effort and expense in doing so is likely to be very high indeed. If the system has been around for a long time, then it is most probably working fine and your users are happy with it (this isn’t always the case, but is more often the case).
What I do feel is valuable though is adding some tests when you need to change/extend part of the code base. As the system already exists and you can’t reliable determine that the code is doing what it should be doing you can write what Michael Feathers describes in his book “Working Effectively with Legacy Code” calls characterization tests. A characterization test is:
“… a test that characterizes the actual behaviour of a piece of code. There’s no “Well, it should do this” or “I think it does that.” The tests document the actual current behaviour of the system.”
What this is saying is you write a test(s) to document the “Current” functionality of the module/component that you want to change or extend. What you are using the test to tell you is whether your modification has changed the result of the current code. In general you don’t want your modifications to change the existing functionality of the existing code, unless that was your intention of course.
Getting a characterization test in place is easy to say in theory, but sometimes there are not any decent hooks into the code base to inject the tests. This is where you may need to do some refactoring to create some layers or seams in your code. You do have to be very careful though as the act of putting in a layer or seam could change the behavior of the code. Mocking libraries can help you out here though. I have never normally been a fan of mocking libraries for writing new code, but they are invaluable when you have to work with old legacy code that has no tests.
So to summarize, I don’t think there is any value in spending time wrapping the whole of a legacy system in unit tests as this can be very costly in developer’s time let along financially expensive. When you do need to change/modify older code though, it is worth trying to write characterization tests that document the behavior of the code you are trying to modify. There are many of techniques for helping with this including dependency injection, layering and adding seams. Also the use of Mocking libraries can really help with this.
I highly recommend the following 2 books which teach you all about working with legacy brown field applications. The first is “Working Effectively with Legacy Code” by Michael Feathers and Brownfield Application Development in .NET by Donald Belcham and Kyle Baley.