Saturday, 16 January 2010

Dependcy Injection and using an IoC container for Unit Testing not Functional Testing

I had to justify at work that there were benefits to using an Inversion of Control container for benefitting Unit testing.

The argument follows in the letter; Comments please.

Hi I'm emailing this to you to let me know what you think it to convince developers to use an IOC container for DI to facilitate TDD.

Many tests that are called unit tests are actually functional tests in that the method under test is in a class that uses other classes and these classes in turn use even more classes.


See href="

To me a unit test should test a method as that is a true unit. Especially if you are doing TDD as private/internal methods needed to be tested too. Not just APIs of classes and if a high target test coverage is to be measurably achieved then limiting tests to APIs of classes is inadequate.

Now because of our classes using classes that in turn use even more classes, what happens is to test a method we have to satisfy all the dependencies that our method uses not only in the class the method sits in but in all the other classes that method depends on. Thus creating a dependency chain.

These other classes which are dependencies are rarely written by one developer in fact they are often written by several developers of varying abilities and in doing so progamming methods are not employed that facilitate dependency injection.

In particular:
1) Avoiding the instantiation of the dependency in the class itself by the use of interfaces and then using setter or constructor injection.
2) Writing code like creating a dependency chain and tight coupling.
3) Using Flex's data binding mechanism to pass dependencies through the application instead of setters/getters.
4) Creating dependency chains by having in ClassX object1, in Class Y object1.object2 where object1 was got from ClassX and in Class Z object2.object3. where object2 was got from classY.

This fourth one is particularly subtle in that only when you look at the classes as a set can you often see the problem.
And often arises when we pass a complex object from a service into ClassX and then pick apart this object in classes Y and Z it would also help if we had shallower objects rather than deeply nested ones requiring this picking apart.

To alleviate these it's useful for a class to contain only the things it must have and nothing else.

To develop with this in mind given that we are often given complex objects with sub-objects need parsing and refining before we can use them in a particular method being tested. Often we use a delegateStub that passes in this complex data that is then put through the uravelling process in 4), and then passed to the method under test. However this creates functional tests rather than unit tests and hides the need for doing 1).

For effcient TDD only the objects that the method under test needs should be passed in. This isolation is important as it allows to distinguish more clearly what's necessary for a particular method and what's not, than passing in additional unused data. To pass in this data for a test in a way that doesn't require the unravelling of a delegate stub means having stubs for the method under test and being able to inject this stub or the real thing into the method. This is where a IOC Container is useful as a easily modifiable central and shared repository for all the stub types that all the tests and developers are aware of and can use rather than them being created in several TestCase/TestSuite classes hidden away until the next developer happens to come across the class AND the method within that class. The Container highlights and calls out the data and models being injected and facilitates their use throughout the application by simply using the [Inject] metatag.

Now once our classes are set up so that they can be injected into with concrete implementations of interfaces. We can inject stubs or the real classes that are used in implementation and the configuration that a container provides facilitates this in short. A container allows us to move away from functional testing created by 1)-4) and closer to unit testing.


Comments will be appreciated.

No comments: