So here is a question for you, having read some great answers to questions such as Test-Driven Development - Convince Me.

So my question is: "Can Test-Driven Development be used effectively on non-Greenfield projects?"

What problems could a programmer face if they attempted to use TDD in a project that already had non-TDD elements present?

  • 1
    The answer is "Yes." But that isn't really very helpful. What do you really want to know? Please update your question to ask your real question.
    – S.Lott
    Jan 9, 2011 at 14:13
  • @S.Lott: Hey Scott, I really want to know, If people have had experience in using TDD in projects where there was already non-TDD elements present? The problems they had faced etc...
    – JHarley1
    Jan 9, 2011 at 14:15
  • Please update your question with this revised question.
    – S.Lott
    Jan 9, 2011 at 14:16
  • @S.Lott - Cheers all done.
    – JHarley1
    Jan 9, 2011 at 14:21

1 Answer 1

  1. Read Working Effectively with Legacy Code. http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

    Even if it's your project, and not a mystery to you, it's still a kind of legacy. It helps to step back from the code a moment and think about what you're going to do.

  2. Recognize TDD doesn't mean literally "all tests before all code." Having a bunch of code with no test doesn't utterly break the TDD model. It makes things a little more difficult, but it can still be TDD even with zero unit tests available at the current moment.

  3. Start designing with testing in mind. This is actually harder than it appears. But you can do test-driven design and still not quite write all the tests. It requires some experience to design a testable API.

  4. Write one test for one thing that's under development.

  5. Get that one test to work.

Iterate steps 3, 4 and 5 for every deliverable you've got. You're doing TDD for new development only. Legacy remains as it was.

Sometimes, you're rewriting or revising or expanding legacy code.

Also, when you're doing maintenance and bug-fixing, you'll do a slightly different thing.

  1. For legacy code that needs to be touched, try to write a test. Sometimes this is hard because the code is not designed for testability. In which case, you need to refactor the legacy code so you can test it in the first place.

  2. Now that the code you're about to change is structured so it can be tested. You can now (a) run tests on the legacy, (b) revise the tests, (c) revise the code, (d) run tests on the revised version.

You don't try to test everything. Just the absolute minimum to keep moving forward with deliverables.

When you're diagnosing a problem with legacy code, you can also use tests to help with the diagnostics.

  1. Rewrite the bug report into a test case.

  2. If necessary, refactor the likely culprit modules for testing. They may not be designed for unit testing, making it difficult to use TDD.

  3. After refactoring, you have a unit test that demonstrates the bug. Run it. It should fail. Now you're doing TDD. You have a test and code which fails the test.

In the maintenance and diagnostic cases, you're often forced to refactor legacy code. This is sometimes hard because the legacy code may not be designed with unit testing in mind.

The goal is to isolate functionality through "Dependency Injection" (Delegation or Strategy design patterns) and Mock Objects.

  • To achieve dependency injection, you will often decompose legacy code into smaller methods or classes which do less, and and knit together during the main program initialization. Your unit test will knit them together for testing, your main program will knit them together for production work. The important thing is that you often need to decompose.

    Some programmers object to this because a testable API often "exposes too much" or requires "too many lines of code from the client" or some other whining. Those were not measures of quality. Testing has more value than code golf. A few extra lines of initialization in the production application won't kill anybody. A few extra lines of initialization to aid testing may save mountains of diagnostic and maintenance effort in the long run.

  • Building mocks -- in some languages -- is an art form. There are libraries and frameworks for this. Languages with static type declarations (Java, C#, C++, etc.) require sophisticated mocks. Languages with duck typing (Python, Ruby, etc.) don't require fancy mock libraries.

  • +1 Superb response. So much more than what I would have written! Jan 9, 2011 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.