After all the good things you heard about it, you have taken the plunge and are taking your first steps in test driven development. You thought about what your
Splendiforous class is supposed to do, wrote the tests and grew the implementation for all its methods.
This TDD thing really makes sense. Yay!
You’re on a roll and start work on the
But then… you hit a snag.
Working on the
Magnificent class you realize that
Splendiforous isn’t quite doing what
Magnificent needs. When you change the
Splendiforous to meet
Magnificent‘s needs, your beautiful tree of green test results turned an ugly screaming red. When you don’t change it, you can’t get the tests for
Magnificent to pass.
Catch 22 if ever there was one.
Should you fix the broken tests?
Should you have kept the original
Splendifourous methods and added different ones to cope with what
You scratch your head in bewilderment. This TDD thing looked promising but if it is going to make you fix tests at every turn you take, it is hardly worth the trouble?
Why is everybody else so smitten with it then?
Let’s take a step back. Couple of things going on here.
You were writing your tests before implementing the methods. Good for you.
But when the requirements for
Splendiforous changed, you jumped right in and changed the implementation. Hardly test first now, is it?
You probably should have changed the tests to reflect the new requirements. Tests going red when requirements change is normal. It signals that the implementation doesn’t meet the requirements after all. And then you fixed the implementation so the tests go green again signalling that the implementation once again meets the requirements as reflected by the tests.
But is that all there is to TDD?
Why did you start with
How did you know that you needed a
Splendiforous or what it was supposed to do?
The traditional, non-TDD, of doing things is to code all the bricks you need before throwing them together to form a wall. Unfortunately, quite often your bricks don’t play nice with each other and constructing your wall takes quite a bit of reshaping your bricks to make them all stack nicely. You repeat this for every wall in your building and do it again when you come to combining your walls to create the house that you set out to build in the first place.
This inside-out approach is turned on its head when practicing TDD.
Turning on lights
Instead of starting on the inside, you start on the outside: the house’s external appearance.
You think about what the house should do (or be), what can go into the house and what should come out of it. You define your house as a single black box and cut out doors and windows to get stuff in and out of your house.
For each of those doors you decide what should happen when something tries to enter. And you write tests to verify that the house responds appropriately.
To make the tests pass, you turn the black box that represents your house into a white box by deciding what rooms and corridors it needs to serve its purpose. How these should be laid out and how these should be connected by doors.
And you don’t go into the rooms yet. Each room is a new black box. And each black box only gets the doors and windows required by what the house needs it to do (or be).
Of course you can’t test your house without giving each room at least some lighting, turning it gray instead of really black. This is were test doubles come in. Each test double only gets enough lighting to verify that the house responds correctly to what it gets back as it sends its inputs through the appropriate rooms.
Back to your predicament
Practicing TDD would have had you starting on the outside. With
Magnificent. Defining what
Magnificent should do, writing the tests for it and making the tests pass would have brought the need for
Splendiforous to light. And would also have dictated the requirements for
You would not have had to rack your brain to try and come up with what might be expected of
Splendiforous. You would not have had to realize later that what you had concocted wasn’t quite what was needed. And thus the question of fixing tests or adding methods would not even have come up.
So what’s the takeaway for all this?
When you find yourself “thinking up” what some method or class should be doing:
- Take a step back and figure out what code will be calling it. Rinse and repeat until you are at the edges of your product with its environment: its interface with the outside world.
- Work your way back in defining the requirements for each black box as you go.
- Do it breadth first. So you can evaluate how things fit together and whether that feels awkward or good.
- Fix (refactor) anything that feels awkward (in the wrong place for example).
- Only then take the deep dive into each box, illuminating all its details.
That’s it. Now you know how to stop yourself from thinking up code that you don’t need and you won’t have to question what to do with it.
Now go out there and put a sticky on your monitor that reads: “Turn on lights from the outside in” and look at it whenever you catch yourself “thinking up” what some code should do.