Clarifying the Goal of Behavior Change
Predictive Test-Driven Development: Part 1
Originally published on . Last updated on .
Change Behavior: adding capabilities and features to a system, or, perhaps, fixing bugs. The goal is improving the lives of the folks that the system serves.
Increase Changeability: refactoring, or re-organizing the code to make it easier for us, the developers, to understand the code and make it easier to Change Behavior (now or later).
We’ll first look at the Change Behavior part of the cycle, often represented by the “Red-Green” parts of the TDD diagram:
When I do (and teach) the Red-Green part of the TDD cycle, the steps I actually perform are more than two:
Let’s start with the first two steps, which seem simple enough…
Before I did TDD on a regular basis, it always amazed me how much code I could write without being clear about the behavior I was adding. This became really obvious after I resumed my training work years ago. It was common for students to get stuck, so I’d ask them “what are you trying to do here?” Often, they couldn’t explain it, or did so in a way that was vague. After some back and forth, once they were able to verbalize the goal clearly, all of a sudden they knew what was wrong and how to proceed.
Therefore, being clear and precise—in a way that a unit test requires—helps you make sure you know exactly what you need to do. However, that’s not enough for a unit test. You have to be able to observe that the system does what you want it to do, i.e., you have to answer the second question: How Will You Know It Did It?
For example, if you’re thinking about the micro-feature (or “story” if you prefer):
The player gets a new card when they draw one from the deck
How will you know this worked? What, exactly, is the expected (and observable) outcome? Can you access that observation by directly asking an object for information, or do you have to dig into 3 separate objects for details? We’ll need to clarify the assumptions in order to really know.
Let’s start by rephrasing this story in Given-When-Then format:
Given a PLAYER with a HAND containing 2 CARDs,
When the PLAYER DRAWs a CARD from the DECK,
Then the HAND should have 3 CARDs.
Note the use of our DOMAIN terminology: player, hand, card, draw, and deck. Talking, thinking, writing, and discussing the functionality in those terms will not only help us figure out the desired behavior, but make sure we’re using the language correctly with our domain experts.
Now we have precisely defined the behavior we want to add—player draws a card—as well as how we can observe that it happened, by checking that the size of the hand is 3 cards, when it started with 2. (How do we know we started with 2? Sounds like a separate test!)
That’s a lot of work and thinking, but it’s exactly (and only) what we need to get to the next step, which is writing the actual unit test. We’ll look at that in the next part of this series.
I’ve had people say that the two main activities of TDD are writing a failing test and then making it pass, but that’s only a part of TDD and misses the other equally important activity: refactoring. ↩︎
We often refer to solving problems by talking about them out loud, perhaps to a rubber duck sitting on our monitor, as Rubber Duck Debugging, or sometimes just Rubber Ducking. No human necessary. ↩︎