I've inherited a legacy web application that has no unit tests in it. I'd like to add some, but am at a loss of where to start. Should I add them to old code? Or just new code going forward? What if that code interacts with legacy code? What would you suggest?
7 Answers
First, I would recommend unit testing all changes going forward, I think most everyone would agree this is a good idea for regression.
However, for existing code, this is one of those situations where you need to look at how much risk you're willing or allowed to introduce into the product. The problem is that when you start to unit test an existing code base, you're going to soon realize many opportunities for refactoring and design refinement.
Take it from me, if you're a stickler for good design, but you haven't been empowered to make drastic refactoring descisions, you're only going to end up with a broken heart when you try to write tests for the legacy parts -- and yes, if it doesn't have existing test suite its going to NEED refactoring. If you're not allowed to make high impact changes to the production application, you're going to end up implementing something we like to call the "garbage adapter pattern". Good luck!
I would suggest getting a copy of Working Effectively with Legacy Code.
We went through this book in a study group. it was painful, but useful.
Topics include:
- Understanding the mechanics of software change: adding features, fixing bugs, improving design, optimizing performance
- Getting legacy code into a test harness
- Writing tests that protect you against introducing new problems
- Techniques that can be used with any language or platform-with examples in Java, C++, C, and C#
- Accurately identifying where code changes need to be made
- Coping with legacy systems that aren't object-oriented
- Handling applications that don't seem to have any structure
You can see a short into to this at http://www.objectmentor.com/resources/articles/WorkingEffectivelyWithLegacyCode.pdf
is the legacy app layered?
if so, add units tests to the back-end/business layer first
if not, add unit tests to new code going forward, and when bugs are discovered (for regression testing)
if you have the time/ambition to unit test the whole thing (eventually), start a list of features (critical ones first) and add unit tests for those, a few at a time
If it's code you inherited, presumably you have to start reading it and understanding what it does and does not do. I suggest you write unit tests that reflect your growing understanding of the code base. Eventually you will build up a body of knowledge about your legacy application that says 'these functions are the functions that pass those tests' as opposed to 'these functions are the functions that have those implementations'. Then you will have more freedom and confidence to make changes without breaking things.
I wouldn't write any tests just for the sake of having tests. I would only write tests when a bug is discovered or you are adding new functionality. Then write tests to box the code that you need to change/implement to define what it currently does. In the case of a bug, write the test to prove the bug has been fixed. In the case of new code, what it is supposed to do. Now go and implement the fix/new feature. If you find that you're tempted to touch code outside your test "box" -- write some more tests to box that area (or reconsider the changes you want to make). Introduce new tests gradually as needed to maximize the investment you are making in the tests. Writing tests to prove that working code works seems pointless until it's shown to be broken.
If the web app is not unit-tested, it's probably also not easily unit-testable. Putting it under unit-tests can be risky as you do not have [Unit] tests, yes, chicken and eggs. Moreover this takes time and doesn't bring much value to the application.
I'd aim to write end-to-end automatic test with Selenium, Watir, HtmlUnit, or HttpUnit, YMMV for the legacy part of your application. These tests (characterization tests) will pin down your current application behavior, like unit tests do, but from the outside, allowing you to make changes with the ability to detect undesired side effects.
Write unit tests for the new code, and when changing the legacy code, whether it is for fixing a problem, or adding new capabilities.
Write tests at the known "pain points" of the application. Code that breaks often or is generally of higher risk is a good place to start, as it helps to build a front-line of defense in this area and exposes the team to the scope of the unit tests in that application.
Every time a defect is opened against the application, going forward, try to write a unit test to expose this behavior. It will let you know when it is fixed and hopefully prevent it from being introduced again in the future.
Additionally, look for code that needs to be refactored. Any refactoring effort should be prefaced by the creation of unit tests. This helps to ensure that it was working both before and after you have made your changes. Refactoring can be tough in the beginning, because of the risk of the "ripple effect", where one breakage can wreak havoc through the entire application in unexpected fashions.