Use the Dependency Inversion Principle (DIP) to isolate one class from the actual implementation of its dependencies.
Depend upon Abstractions. Do not depend upon concretions
DIP is certainly not an invention of TDD practitioners, but it is employed frequently for no other reason than to enhance testability.
Take a look at this code that does not employ the Dependency Inversion Principle.
public class MessageProcessor1
{
public void RouteOrder(Order order)
{
string queueName;
if (order.IsComplete)
{
queueName = “Queue.Completed”;
order.CompletionDate = DateTime.Now;
}
else
{
queueName = “Queue.Incomplete”;
this.assignOrder(order);
}
OrderQueue orderQueue = new OrderQueue();
orderQueue.SendOrder(order, queueName);
}
public void assignOrder(Order order)
{
// do something to the order
}
}
public class OrderQueue
{
public void SendOrder(Order order, string queueName)
{
MessageQueue queu = new MessageQueue(queueName);
queu.Send(order);
}
}
So what’s so wrong? The MessageProcessor1 class cannot be tested, or function, without the OrderQueue class being executed.
Not only is MessageProcessor1 strongly coupled to OrderQueue, it is coupled to anything that OrderQueu is dependent on. In this case the MessageProcessor1.RouteOrder() method cannot be tested unless MSMQ is available. More annoyingly, any automated test would need to query MSMQ to get the resulting Order object to compare the expected results.
Now, using DIP we make the MessageProcessor2 class dependent upon an abstracted IOrderRouter interface instead.
public interface IOrderRouter
{
void SendCompletedOrder(Order order);
void SendIncompleteOrder(Order order);
}
public class MessageProcessor2
{
private readonly IOrderRouter _router;
public MessageProcessor2(IOrderRouter router)
{
_router = router;
}
public void RouteOrder(Order order)
{
if (order.IsComplete)
{
order.CompletionDate = DateTime.Now;
_router.SendCompletedOrder(order);
}
else
{
this.assignOrder(order);
_router.SendIncompleteOrder(order);
}
}
public void assignOrder(Order order)
{
// do something to the order
}
}
MessageProcessor2 is only dependent upon an interface, and can use any implementation of the IOrderRouter interface (in theory).
Here’s what a unit test for MessageProcessor2 might look like:
[Test]
public void RouteACompletedOrder()
{
Order order = new Order();
order.IsComplete = false;
DynamicMock routerMock = new DynamicMock(typeof(IOrderRouter));
// SendIncompleteOrder(Order) should be called on the incomplete order
routerMock.Expect(“SendIncompleteOrder”, order);
routerMock.ExpectNoCall(“SendCompletedOrder”, typeof(Order));
IOrderRouter router = (IOrderRouter) routerMock.MockInstance;
MessageProcessor2 processor = new MessageProcessor2(router);
processor.RouteOrder(order);
// Call to the DynamicMock to verify the interaction
routerMock.Verify();
}
Now we have our routing logic decoupled from the delivery mechanism. Besides testability, we have added the ability to substitute other delivery mechanisms like a web service, MQ Series, or simply writing to a file.
Mechanically, there is a couple things of note in the unit test above. I used a dynamic mock object with NMock as a stand-in for the IOrderRouter interface to test the interaction. The IOrderRouter instance was passed into the MessageProcessor2 constructor, not created by MessageProcessor2 itself. This is an example of Dependency Injection (Inversion of Control). I will try to blog on both topics soon.
Rules of Thumb to Use DIP
- Anytime your code calls out of the CLR, and maybe just the current AppDomain itself. I would isolate any kind of call to databases, messaging queues, web services, and remoting behind a .NET interface. In ASP.NET or web service applications, always abstract any kind of call to the HttpContext objects. Nothing gets in the way of automated testing like coupling to the ASP.NET runtime. Remember, one of your unofficial design goals is to test as much of the application as possible with your laptop disconnected from the network. Look at the Gateway pattern. As a mild rant, using a Service Oriented Architecture does not guarantee that your actual code is loosely coupled. I’ll expand on that soon.
- Singleton’s. Singletons can throw a pretty serious monkey wrench in your unit tests by maintaining state between unit tests. Here is one way to avoid the Singleton issue I stole from some former colleagues that wrote PicoContainer.
- Interface points between major subsystems. Aggressively use the GoF Facade pattern to limit the exposure between subsystems, and abstract the interface to allow for easier testing with mocks or stubs.
- Extension points. This is a pretty obvious usage, but knowing when to make an abstracted extension point is way, way beyond the scope of a mere blog post.
A great codebase to look at for examples is the CruiseControl.NET application. I think they do a very good job at testing a nasty piece of functionality.