June 07, 2006
DI and TDD: The Dynamic Duo
[Edited 08/15 - fixed broken link (thanks to Julia Renouard)]
I am probably overstating the obvious here, but one of the valuable side effects of practicing test-driven developement (TDD) is that it encourages the use of dependency injection (or is it the other way around?).
What's dependency injection (DI)? In a nutshell, DI means that a class does not instantiates any other classes. Instead, it depends on the classes that are "somehow" injected into it.
This is probably best explained with an example. (While this code is in C#, it is generally generic):
public class Controller
{
IView view;
//old way
public Controller()
{
this.view = new View();
}
//new way - Using Inejection
public Controller(IView tView)
{
this.view = tView;
}
}
(You can read a more thorough explanation of this at Martin Fowler's site.)
The Controller in this example needs a view to operate. Using DI, the view is expected to be instantiated by someone else and the controller will get the view instance when the controller is instantiated. Another option is to use a setter method (the dependency is injected after the class is instantiated). Lastly there's getter injection--but that requires using aspects to weave the dependency into the class.
Why do you need DI? For one thing, it helps introduce loose coupling--using DI, the Controller class above is only dependent on the IView interface and not on the view implementation. Using DI also means we can supply different views (provided they adhere to the IView interface) and the controller won't have to change.
Note that when you use DI, the injected class loosens control on the lifetime of the dependency (in the example above, the Controller no longer instantiate the view). If you need the object lifetime control you can inject an abstract factory instead of injecting the target class.
Furthermore DI makes classes more testable (which is why DI works so well with TDD). In the "old" example above, it is hard to have the Controller as the unit under test since we also need the view. If we use DI, the test fixture (or test method) can give the unit under test (the Controller in the example) a Test Double (dummy, fake, stub or mock) so that the unit under test can be as small as you like.
If you want to use DI, you don't have to build the infrastructure (factories, assemblers, etc.) to support DI by yourself. There are several frameworks that do this for you: spring and spring.net are the most famous, but there are few others like picocontainers, objectbuilder, and so on.
When practicing TDD, you find yourself more and more inclined to used DI as it will makes your tests smaller and easier to write. As mentioned above, when you use DI you also improve the overall design of the class. It is more portable, interface-based programming makes the classes less fragile, and so on, thus as this blog's title says
DI and TDD really are the dynamic duo--with one helping promote the other and vice versa.
Posted by Arnon Rotem-Gal-Oz at 07:30 AM Permalink
|