Test Driven Development (TDD) is often reduced to the three phases
- Red – write a test
- Green – make it run
- Refactor – clean up code
There is nothing wrong about these three steps. But there is more involved in test-driving your development.
Where to Start?
Red-Green-Refactor tells you nothing about how to get started. The question is: which test should I write first?
When I start programming a new component or class then I first have to understand why I’m programming the new functionality. The very first step is to understand the problem and the desired behaviour (call it requirements if you want to).
Only after I understood what behaviour is expected, I’m able to think about a design that could solve the problem.
Hey but wait! Designing? I thought we were talking about TDD here?
It is true that we do not want to invest a lot of time in an up-front design. However, without a bit of initial design, the changes to get something functional are very low. The inital design consists of a very coarse decomposition of the problem in probably several building blocks (e.g. components, classes).
But keep in mind, this is only an initial design and will change. Sometimes this takes only a couple of minutes and happens inside my head – sometimes this takes several workshops with co-workers to get done.
The goal of this phase is to know where to start writing the first test.
One last thing before getting lost in Tests
Why wait, I know where to start? Let’s go coding then.
Unfortunately, most TDDers start with a randomly chosen first test. Followed by any test that comes to their minds next. This behaviour often results in sub-optimal designs, needing additional refactoring time afterwards.
A better approach is to first gather several tests for the behaviour to implement, and to put them on a TODO list. Side-note: I keep my TODO list as // TODO: in my unit test source code files.
Take a minute or two to write down all tests that come to your mind you probably will have to test to make sure the new functionality works as expected.
Which Test to Pick First/Next
Look at your TODO list and pick a test that you are confident of implementing and maximizes feedback for you.
Only a test that can be fulfilled will take you step further. If there are too many open questions regarding a feature a test will check, wait until you have gathered more information with other tests.
You should pick the test that provides maximal value. For example because it probably will have the biggest impact on the design. Or because it will rise your confidence in writing some other tests.
Huh oh! That was not was I expected!
If you’ve picked a test you were confident to get right and find out that there is more to it as expected then you may choose to step back. Either pick another (simpler) test or try to split the test into smaller sub-tests (if possible).
Adding Tests along the Way
Whenever I think of a new test then I add it to the TODO list. There may still be more “important” tests on the TODO list that I’ll pick before the test I just came up with. The TODO list makes sure that I won’t forget the new test.
Example – Bowling Kata
I assume you know Uncel Bob’s Bowling Kata (PPT).
Here are the tests and the ordering that I would take to solve the problem:
- Throw one ball and get Score – tests basic interface and makes me confident to go to next test
- Throw two balls (no spare/strike) and get Score – hey at least I can count something and I get even more confidence and knowledge about the problem domain
- Strike – because a strike frame consists only of a single throw this has a bigger impact on design than a spare frame. I don’t want to get a solution that works perfectly for 2-ball-frames and then implement the special case of a single ball. The risk that my design has to be changed dramatically at that point is to high for me.
- Spare – not much new in here after normal and strike frames
- 10th frame – the 10th frame is special because it may consist of three balls. Test normal/spare/strike in 10th frame.
- Optimal/worst game – make sure that the algorithm works in extreme cases
What is your TDD strategy? Write a comment! Thanks in advance.