- Attacking the Dragons
- Implementing a Test Harness
- Making the Changes
- Test, Code, Test
- Any Dragons Remaining?
Implementing a Test Harness
When refactoring a legacy code base, first you must do everything possible to add test coverage to highly unstable classes before making any changes to the code. Michael Feathers' book Working Effectively with Legacy Code is a great resource for finding the best way to test systems that were not designed for testability at the time of their creation.
Why create the test harness? If we're going to make changes, especially to something that's complex, we need a means of knowing quickly if the change is obviously wrong. For anything but small cases, no test we can design will provide mathematical proof that no defect occurred based on the change; therefore, having a suite of automated tests around the code we intend to change can increase the likelihood that we'll detect defects.
Once you have a test harness, what do you do with it? It's important to run the tests frequently[md]ideally, every time you build the software. Of course, this approach can be tricky; legacy code is likely to have tests that would be unrecognizable to most people who do frequent unit testing. It's not uncommon for legacy code to have complex business logic intermingled with database or other types of integration code. Thus, on the first pass, most of the tests will be integration tests, rather than pure unit tests.
Personally, I'm in favor of having such integration tests, initially as part of every build that happens when new code is committed to the code base. Such tests can be moved to a less-frequent schedule once the changes have proven stable, and eventually be replaced by unit tests that isolate the system under test in a cleaner manner. However, in the short term, during initial refactoring, you want frequent feedback every time you build the software.