How To Write Testable Code - 11/5/2010
Test Driven Development is great if you know how to do it right. Unfortunately many of the tutorials and training resources available skip right over how to write testable code because being samples they tend to not involve the typical layers you find in real code where you have services layers, data layers, etc. Inevitably when you go to test your code that does have these dependencies, the tests are very slow, difficult to write and often break as the underlying dependencies return results other than expected.
Code that is well written is separated into layers with each layer responsible for a different slice of the application. Actual layers vary based upon need and also upon developer habits but a common scheme is
- User Interface/Presentation Layer This is your presentation logic and UI interaction code.
- Business Logic/Services Layer This is your business logic. Ex, code for a shopping cart. This shopping cart knows how to calculate the cart total, how to count items on the order, etc.
- Data Access Layer/Persistence Layer This code knows how to connect to the database and return a shopping cart or how to save a cart to the database.
- Database This is where the cart's contents are saved
Without Dependency Management
Without dependency management, when you write tests for the presentation layer your code hooks into real services that hook into real data access code and then touch the real database. Really when you are testing the "add to cart" feature or the "get cart item count" you want to test the code in isolation and to be able to guarantee predictable results from your code. Without dependency management your UI tests for "add to cart" are slow and your dependencies return unpredictable results which can cause your test to fail.
The Solution is Dependency Injection
The solution to this problem is dependency injection. Dependency Injection or DI often seems confusing and complex to those who haven't done it but in reality it is a very, very simple concept and process with a few basic steps. What we want to do is centralize your dependencies, in this case the use of the ShoppingCart object and then loosely couple your code so that when you run your app it uses real services and when you test it you can use fake services that are fast and dependable. Note that there are several approaches you can take, to keep it simple I am just demonstrating constructor injection.
Step 1 - Identify Your Dependencies
Dependencies are when your code is touching other layers. Ex, when your presentation layer touches the services layer. Your presentation code depends on the services layer but we want to test the presentation code in isolation.
Step 2 - Centralize Your Dependencies
While there are several ways this can be done, in this example I am going to create a member variable of type ShoppingCartService and then assign it to
an instance that I will create in the constructor. In each place where I use ShoppingCartService I will then re-use this instance rather than creating
a new instance.
Step 3 - Loose Coupling
Program against an interface rather than against concrete objects. If you write your code against IShoppingCartService as an interface rather than against the concrete ShoppingCartService, when you go to test you can swap in a fake shopping cart service that implements IShoppingCartService. In the image below note that the only change is that the member variable is now of type IShoppingCartService instead of just ShoppingCartService.
Step 4 - Inject Dependencies
We now have all of our dependencies centralized in one place and our code is now loosely coupled to those dependencies. As with before there are several ways to handle the next step. Without having a IoC container such as NInject or StructureMap setup the easiest way to do this is to just overload the constructor.
Step 5 - Test with a Stub
An example of a possible test fixture for this is below. Note that I have created a fake(aka stub) of the ShoppingCartService. This stub is passed into
my controller object and the GetContents method is implemented to return some fake data rather than calling code that actually goes to the database. As this
is 100% code it is MUCH faster than querying a database and I never have to worry about staging test data or cleaning up test data when I am finished testing.
Note that because of Step 2 where we centralized our dependencies, I only have to inject it once. Because of Step 3 our dependency is loosely
coupled so I can pass in any object real or fake as long as it implements the IShoppingCartService interface.
Where do I go from here?
- Use a IoC/DI container. The most common and popular IoC containers for .Net are StructureMap and Ninject. In real world code you are going to have a lot of dependencies, your dependencies will have dependencies, etc. You will quickly find it becomes unmanageable. The answer is to use a DI/IoC framework to manage it.
- Use a Isolation Framework. Creating stubs and mocks can get to be a lot of work and using a mocking framework can save you a lot of time and code. The most common for .Net are Rhino Mocks and Moq