Andrew Stopford's Weblog

poobah

News

Articles

Family

Old Blogs

March 2008 - Posts

The design in TDD

Lately I've been drafting posts on my daily commute and post them up in batches (just in case you were wondering :).  

I recall an article (not sure where I read it) about Ron Jeffries and his ability as an 'alpha architect'. Such people are rare, the design they have mostly in their mind with TDD providing a way of slightly reshaping the design and proving the model (in a pair session that can mean validating each other’s ideas). Another kind of folks is the folks that have a general system picture but design a piece at a time with model shaping and validation occurring as they go. Both are no less a way of designing and developing a system but while the 'alpha architect' has considered overall, system considerations and won't introduce design faults, the other folks need to go carefully to avoid those faults.

The point that TDD is a design process seems to get missed, writing tests before your code is only part of the process and not the sum total. The trouble I have found is that the design process tends to be an organic one, a rhythm that you adopt and you’re not aware of what process you’re following. So in this post I am going to attempt to lay out some of those considerations, not sure I will capture them all and you may have your own, leave us a comment with yours.

SOC

A car is not made from one part but many, with each part such as the engine or wheels made up from other parts. Does the engine depend on the door handle or the windscreen wiper blades? No. Each part, object or concern is separated from the rest but is associated in such a way that you can still achieve your aim without any dependencies on each concern. Relating that back to code, if a car class had an internal Ferrari engine object could we create a Porsche car from that car class? If the car class held a reference to an engine object that the Ferrari object was sub class off could we instead create a Porsche sub class and our Porsche car have the right engine? The practice of SOC is really a good OO practice in that you ensure your classes are so tightly bound to other concerns that you losing all the polymorphic benefits.

TDD helps us here in that we look at all of the concerns of the class and how that class behaves in isolation and with associated concerns integrating with the class. If in writing and then developing the test it fails randomly, your setup and surround is heavy or you are forcing the test into a deep integration test then chances are the class and its concerns are bound up too tight and it's a candidate for further refactoring.   

Integration and Isolation

Take your class in isolation, can you isolate it? If you can what are its dependencies, if you’re testing it what kind of dependencies do you need and how deep are they. The more complex it is to take a class in isolation then your likely looking at a smell and some further refactoring is required. TDD forces you into putting a class into isolation and not being 5 levels deep in a system. You’re focused on that class alone and once taken out of context you can really learn if it will hold up.

Jeremy’s most recent post talked about using static and integration styles in your tests, it's a great post and well worth taking your time over. One thing that is worth mentioning about integration is that integration means as light as you can make it. If your using a lot of other actual concrete concerns to achieve isolation then it's a smell, the cohesion between your class and its concerns is too tightly bound. If you can use mocks or doubles in place of the concerns and your class knows no difference, working as it would normally then you’re in a happy place. Sometimes you can't avoid using an actual and sometimes it makes sense but too many and too often would be a smell, finding a balance is half the skill.

Things around

Consider the concerns around the class, what does the class need as inputs, what does it need for its processing and for its outputs. Those considerations would help you decide what you want to mock, double or actual. For example a class adds an entry to a database as a net product, a test (or for that matter another concern making use of the class) does not care how this product occurs; only that it has occurred. It would be a good mock candidate as we are not reusing the db entry further down the test process and as such we only care the method has occurred. If you wanted to emulate some kind of processing or validation, for example loading a collection of data, then a double could be useful. Following the same contract as the actual, the double would be a light weight emulation that the test could use.

Cross Cuts

Cross cutting concerns are (but not limited to) things like logging, security and validation etc. Things that may not be central to the core parts of the class like processing or logic but needed in the general scheme of the operation of the class. If you find your concerns reusing other concerns more and more often than these could be a cross cuts. To aid re-use and isolation you will want to ensure the cross cuts don't affect the SUT when under test. There are several tools are you disposal (such as DI, IoC and AOP) to service the cross cuts.  

Microsoft MVC and MbUnit

You may recall ScottGu's post about the new feature in latest drop of the MS MVC framework that allows you to pick your unit test framework of choice. With Mix08 now wrapped up and the framework released I can show you what the MbUnit templates look like. You will need either MbUnit 2.4.2 or MbUnit 3.0 alpha 2, early previews of these are available if you really like the edge, but more stable drops are coming, with 2.4.2 installed here is what you will see.

When created you would see


The test project depends on the name of your project, for example "myproject" would be "myprojecttests" etc. The project is preloaded with your main project and a referance to the MbUnit.Framework dll. For the moment we have a default test for the controller, which looks like.

using MbUnit.Framework;

[TestFixture]
public class HomeControllerTests
{
    [Test]
    public void About()
    {
        //
        // TODO: Add test logic here
        //
    }
    [Test]
    public void Index()
    {
        //
        // TODO: Add test logic here
        //
    }
}
 
Notice default tests for your about and index page, these match the default about and index controllers. The template only referances what you need so you don't need to refactor out any unrequired referances.

Note that the MVC team has blogged about how to add and use NUnit templates including NUnit\Rhino templates. To save you some steps we have added the templates to the installers, I did an inital drop of the template but Jeff did all the work to make this happen and we would welcome your feedback.

More Posts