Refactor - UnitTest - Design Trilemma in Legacy Code

Tag: unit-testing , design , refactoring , legacy Author: jack3231 Date: 2011-11-11

How do you tackle this problem when you are dealing with legacy code

  • Classes you deal with is not well designed, requires some serious design changes
  • Classes you deal with mostly tightly coupled
  • You don't have enough unit tests to do refactoring much
  • You don't add new unit tests because design is bad and you are going to change it anyway
  • You can't change the design that easily because
    • Tight coupling, not enough unit tests - stuff can really wrong as it requires a new design for multiple classes at the same time without any safety nets.

Where do you start? How do you attack to the problem?

Other Answer1

A Chicken and Egg problem.

If it's not possible to write some decent unit tests because of thight coupling, a better approach might be to work from the top down.

If you don't already have Integration, System and or GUI tests, this would be a good reason to create them before you start creating unit tests. Once you have them in place, you can start refactoring the code to create decent unit tests and still be fairly confident that your all-compassing tests will catch your most obvious mistakes.

Note that in my personal opinion, these testcases should be created as such that they should not have to be altered once you are ready to start creating unit tests and refactoring your code.

A must read on this subject is Working Effectively with Legacy Code by Michael Feathers.


The strategy that I’ve outlined works for a wide variety of changes, however there are some caveats. Sometimes the only decent inflection point that you can find for a set of classes is the system boundary. In some applications, the system boundary can be pretty wide: it encompasses the GUI, calls to other external libraries, the database, etc. In those cases, the best way to get an invariant is to start writing what Steve McConnell calls “smoke tests” against the rest of the system


Feathers expanded his article into an excellent book: Working Effectively with Legacy Code

Other Answer2

You first want to make sure you don't change the behaviour of the code. If you write unit tests which assert the way that the code currently behaves then you can make changes to the code and check that the tests still pass, if they do then the code behaviour hasn't changed. You can then refactor the code however you like. When you refactor the design you refactor the tests as well, but don't change the assertions they make.