Allowing tests to drive development


I have noticed teams who are new to TDD reach for ever more sophisticated tools to help them test their code rather than refactor their code to make it more testable. By doing so, they lose one of the major benefits of TDD: the feedback it gives on internal design quality.

Code is difficult to test when classes are tightly coupled, are entangled by hidden dependencies like static methods or singletons, or don't clearly distinguish between internal implementation details and peer objects (dependencies, policies or notifications). To unit-test classes like this, it can be tempting to use tools that use clever class loader tricks and bytecode manipulation [1, 2] to break the tight dependencies. But by using tools like this you lose one of the major benefits of unit-testing in the TDD process: rapid feedback on the internal quality of the software.

TDD is not merely about testing - that is, about verifying the external quality of the software: whether it is reliable enough, accurate enough, fast enough, etc. It also gives you feedback about its internal quality: how well designed the code is, such as how loosely coupled and cohesive the classes are, whether dependencies are explicit or hidden, whether information hiding is being used appropriately. And that translates into feedback about how maintainable the code is: how easy it is to extend, change or correct its behaviour over its lifetime.

If you use unit-testing tools that let you side-step poor dependency management in the design, you lose this valuable source of feedback and, when you find that you do need to address these design issues because you have to modify the production code, it will be much harder to do so. The poor structure will have influenced the design of other parts of the system that rely upon it. The programmers responsible for the change will not understand the code as well as those who wrote it (even if they are the same people). It's far easier to nip these design issues in the bud as you discover them than let them remain to affect the design of the rest of the code.

I therefore use a simple rule of thumb when choosing technologies to help me write unit tests:

Break dependencies in unit tests only with techniques that would be acceptable in the production code.

Would I use classloader magic and bytecode manipulation to set dependencies in production code? No. Would I use reflection to modify private fields in production code? No. Would I refactor to introduce an interface between objects? Yes. Would I refactor an object to get a dependency through its constructor instead of from a global variable? Yes.

Following this rule of thumb ensures that code is always easy to change. There are no nasty surprises late in the project when new functionality requires massive changes to the design and developers are pressured to hack the change in because the estimated costs of doing it the right way are too high.

Copyright © 2008 Nat Pryce. Posted 2008-01-06. Share it.

Comments powered by Disqus