Sunday, December 30, 2007

BDD/TDD - What Drives The Domain Design

In view of the discussion of BDD[1][2] I've started to look at the way we specify/test our domain model. A great promise of BDD is that your tests will drive the design of your domain model and that the tests themselves will help explain the design of the domain.

My efforts to get to the essence of BDD have left me very confused and in an effort to better understand BDD I actually went to the Test Driven Development Yahoo group. I've been subscribed to it for a while but I'd never realized how good it is. Well informed discussion and heavyweights like Ron Jeffries/Kent Beck wading in. Great stuff and seems to me to have a similiar feel to the DDD group.

Anyway I came across one particularly useful thread. It doesn't really cover BDD directly but it does cover state/interaction testing and how they influence the way you design. In particular it contains a couple of great posts including this one which tries to get the to real essence of interaction and state testing and the way they affect your design.

I was already well aware of the difference between state and interaction testing but this single post summed things up very nicely and reminded me of a few things.

Personally I refactor, including to patterns, a lot. As I do I find my code changes massively which affects the way I test. Let me give you a representative example...

Example - State Testing/Refactoring Driving The Design
We need to allow people to debit Accounts in our system, a test to kick off this work might be

Account sourceAccount = ...;
Account targetAccount = ...;
Money amountToDebit = ...;

Money originalValueTargetAccount = targetAccount.Balance();
Money originalValueSourceAccount = sourceAccount.Balance();

FundsTransferService.Transfer(sourceAccount, targetAccount, amountToDebit); // code under test

Assert.AreEqual(originalValue - amountToDebit, account.Balance);

I'm not saying this the exact test I'd start with but its representative of the sorts of tests I'd be writing. This is a pure state test and in Ron Jeffries terminology is testing functionality not sequence.

As I go I'd be writing more and more tests and more and more code. As I went I'd refactor and after a while the method may be delegating to Specifications, Rules, Method Objects, Strategies, Entities, ValueObjects, Factories and other classes to do its work.

Some of the new classes will merely exist to ensure the code reads well (pure fabrications in Larman terminology).

As I extracted these pure fabrications I probably wouldn't change the tests, leaving them at the level that they started at (FundTransferService). I could change the tests to be tests specific to the new rule though. I'd just extract the code, ensure the tests passed, refactor the tests, ensure they passed...but then pure fabrications are very much open to redesign at any time so I'm not sure it makes so much sense to write tests for them specifically (questionable?).

Anyway what this means that I'm doing top down development (starting at public interface to the domain) but I'm not using stubbing or interaction testing, instead I use state testing and evolve the code and refactor a lot until I'm pretty happy.

Alternative - Interaction Tests Driving The Design

So the question is what is driving me to change the design, if I was doing proper interaction testing then I would be defining the interactions and then writing the code to fulfill it.

I could probably do this for the high level interactions, for example between entities. I'm not sure I want to include that information in every test but I could use it as a design technique and encode it in a few tests. This is quite high level interactions so I avoid the risk of overspecified software.

Ultimately I would still need some state tests (see this thread for a good discussion of the reason you still need state tests) but I'd be using interaction testing to drive my design.

However as I say I find a domain model is filled with all sorts of little domain classes that only exist because we chose to refactor the code to make it read better, these classes are not necessarily part of the ubiquitous language and are purely an imeplementation detail (they are pure fabrications).

Making interaction testing work for these pure fabrications seems to me to be a bit of a bad idea, these classes exist because of refactoring and I cannot plan for the interactions with them upfront.

Maybe if I stick to only using interaciton testing for the high level interactions, rather than interactions with pure fabrications, I'll get the benefits of interaction testing without the costs. I think thats what I'll try next.

Share This - Digg It Save to Stumble It! Kick It DZone

No comments:

Post a Comment