Test-Driven-Development (TDD) is a concept that I have always struggled to get my head around. Testing before you have anything to test does not seem to make any sense. However I have spent the last few weeks working in an environment where TDD is used, and am now completely enlightened. Testing is clarification that you have achieved what you set out to achieve. Before you can do start anything you have to have a goal / aim of where you want to get to, so it follows that this can be a test.
By writing the test before the code you ensure that:
- You achieve the outcome you are aiming to achieve
- You keep it as simple as possible
So the first point is obvious. If the test is written saying what you want to achieve, then you can easily prove you have done so. A test that passes is the emotional certainty that you (the developer) have done your job correctly. The test then acts as documentation, which can also be traced back to the initial requirement that was supplied by the analyst. Thus no time wasted on additional documentation.
The second point is perhaps more important still and is what makes TDD so elegant and neat. It ensures that the developers only write the code that is needed to pass the test. They do not need to spend ages thinking about what else the system should do, which means they can spend longer thinking about how to write the functionality that is required in the best possible way.
Moreover it means simplified design. You only add as much design to a system as you need to pass the current tests with clean code. Thus there is no commitment to any design until the code calls for it. So the design fits the code rather than vice-versa.
Indeed Kent Beck emphasises that there are only two simple rules to TDD:
- Write new code only if an automated test fails
- Eliminate duplication
This means you design organically as the running code provides feedback between decisions. You can rapidly respond to small changes. This is why it works well with object-oriented designs – it means you can build small blocks with the certainty that everything before works. Designs are made up of highly cohesive, loosely coupled components.
It is only a short step between this and Domain Driven Design (DDD). I am almost confused about the difference between the two. Surely if TDD is done properly they are the same thing? Certainly the line is blurred. I will follow this up with a piece on DDD (which I am pretty certain is the best and most logical way to develop anything).
How does TDD actually work?
So you have to write a new piece of functionality. There are three steps for this:
- Work out what you want to achieve
- Write a test for it
- Write the code
There is more than one interpretation of this. Following the agile development pattern, each of these steps can be carried out by different roles. The analysts work out what the functionality needs to do. The testers translate this into a test. The developers write the code to pass the test. However Beck stresses the importance of having the same person write the tests as the code.
This is evident when we draw comparisons between the steps above and Beck’s Red / Green / Refactor “mantra” of TDD. According to this, TDD involves the following steps:
- Red – Write a test that fails (and may not even compile) as you have not yet written the function
- Green – Write whatever code is needed to pass the test
- Refactor – Get rid of any duplication that you had to put in to get the test to pass
Here the whole testing / development process is done by the developer. This is partly to make things quicker – if everything is broken down into small, discrete pieces of functionality then you do not want to be waiting around for someone else to write tests for you. This then frees up the testers to do proper QA work after the function has been written.
The fact that there is TDD is open to interpretations can only be a good thing as it means that it can be adapted to best suit the needs of your team. Indeed Beck admits that it is a slightly fuzzy concept as it is aware of the gap between decision and feedback during programming, and being able to control that gap.
There are certain programming tasks that cannot be driven solely by tests, for example security software (where human judgement is also needed) and subtle concurrency problems that cannot be duplicated by running the code.
However it seems unlikely that I will be attempting tasks such as the above anytime soon, so until that happens, I will be doing my doing my utmost to write tests before I attempt to write any code.