February 11, 2014

...Learn TDD with Codemanship

My World-Famous Test-driven Development Kitchen Design Metaphor

When people ask me "what is Test-driven Development?", I have a goto kitchen metaphor, which I find quite useful.

Imagine you're asked to design a kitchen. There are different ways to go about it.

You could take a "shopping list" approach, and think of all the features your kitchen should have. e.g., It should have a fan-assisted oven, it should have a microwave, it should have a sink with hot and cold running taps, it should have power sockets, it should have work surfaces, cupboards, a small horse, some sausages, and so on.

There are many, many things your kitchen could have - an infinite universe of possible kitchen designs.

Some will be better than others. But what do we mean by "better"?

Ultimately, kitchens are best designed for people to use. So, in this context, "better" may well mean "more useful".

So we can begin to narrow down the choice of designs by asking the question: who will be using this kitchen, and what will they be using it to do?

Instead of listing things this kitchen should have, we begin by thinking about ways this kitchen will be used.

We identify who will be using the kitchen - e.g., Mum, Dad, the kids, the dog - and then we work with them (okay, maybe the dog gets a business analyst) to understand how the kitchen can meet their functional needs.

A powerful way to do this is to gather together real examples of the kinds of things they would want to do: e.g., prepare a cooked breakfast for four people, make a midnight snack of cheese on toast for one, give a dinner party for 8 guests with a specific starter, main and dessert etc.

We can think of these examples as tests. Our kitchen design is only correct if it passes these tests.

Working one test at a time, we then design the kitchen specifically to pass each test.

We might start with the simplest test - making cheese on toast - and ask ourselves "what's the least we need in our kitchen to do that?" Our initial design might then be a toaster and a grill, one plate, one knife and a cheese slicer. That's all we need to make cheese on toast for one person.

Then we move on to another test. What would our kitchen need to make cheese on toast for one AND to prepare a cooked breakfast of frieds eggs, bacon, sausages, mushrooms and toast with butter? We might add a frying pan and a stove with one ring to heat it on, as well as plates and cutlery for the whole family as needed.

And on we go, adding bit by bit to the design one test at a time, always checking as the design evolves that all our tests are still passing and that we have a working kitchen.

As with all design, the devil's in the detail. Not all toasters are born equal. Not all stoves perform in the same way. As part of our design process, we may well wish to isolate these elements of our design and check independently that - in the context we intend to use them - they will do what we need. Your cooked breakfast could be ruined if the toast keeps burning, or it could end up a lukewarm gloop if your hob isn't hot enough.

We can think of the design details as a chain of dependencies - things that have to happen in a certain order (e.g., we must put cheese on the toast before we put it under the grill, we must toast the bread before we put cheese on it, we must slice the cheese before putting it on the toast, etc)

So it's desirable, rather than tackle each example in one big design "lump" - where there could be many details that might potentially go wrong - we drill down into the design, driving out the details of how our kitchen will pass the test (e.g., is the hob big enough for the frying pan?), repeating the test-driven process down through the chain of dependencies that are involved in making it happen until we have a process that works end-to-end.

So, one test at a time - one example of usage at a time - we "grow" our kitchen design, always checking that every addition or change we make to the design hasn't broken any of our tests.

Of course, such growth, if entirely piecemeal, could lead to some pretty crappy designs. We may spot as time goes on that we've somehow ended up with two fridges, or that the power sockets are right above the sink where water's likely to splash, or that our kitchen consumes enough electricity to power a small town, or that the only place for the dishwasher is on top of the washing machine. And one thing we must be particularly vigilent of throughout is how difficult it will be to change in the future - since, no matter how good your kitchen design is, there will always be room for improvement, always needs we didn't anticipate, and, well, hey... things change.

To avoid making a mess of our design as it emerges, we will need to continously revisit design decisions in light of a variety of non-functional requirements like running costs, ease of maintenance, safety, energy consumption, ergonomics, and even aesthetics. The test-driven approach requires us to continuously restructure and refine our kitchen design so that it not only does it do what they need, but does it in a way they can live with potentially for years, and that will allow them to adapt and extend the design to meet future needs.

In essence, that's Test-driven Development.

Well, maybe.

Posted 6 years, 10 months ago on February 11, 2014