This post is about how TDD informs analysis and stimulates learning. We'll go through a simple example and investigate rationale and mechanics of decisions taken.
The boards starts empty
Let's imagine we're writing a virtual Kanban-like board (this is also called a 'scrum board' AFAIK). If you don't know what a scrum board is: the board is used for task tracking and is split into vertical columns that represent task progress (like NOT STARTED, IN PROGRESS, DONE). It's also split into horizontal categories that represent bigger units of work (often user stories) that the tasks belong to. We usually add a task to the story and watch it pass through the vertical columns until we reach the DONE state.You can read more on scrum boards if you're unfamiliar with the topic, however, what I've written here is enough knowledge to push forward with our example.
There is only one issue: how to start? We could do proper requirements analysis or commonality-variability analysis or something along the way, but this time, let's just start by writing a spec (AKA unit test). What's the simplest behavior we can come up with? Let's see... it would be:
The board should start off as empty.
This is written in plain English (we'll investigate another notation in a second), but that's enough to get us started. Using the above statement, we can put together the first spec:
[Test] public void ShouldStartOffClean () { var board = new ScrumBoard (); Assert.IsTrue (board.IsEmpty); }
The first time I wrote this spec, it seemed (wrongly, but that's coming in a minute :-)) silly to me. Why? Because:
- It didn't let me discover any new collaboration and any new abstraction other than ScrumBoard, which I already knew I was going to need.
- It verifies a public flag is set on the same object that is specified. It looks like cheating - we consider X did something when it tells us so
- It's only a flag, nothing more. No domain logic is needed to implement it
- I can't imagine this flag used anywhere in the application. Even when I write a GUI that displays the board, it's unlikely that it's gonna need to check whether the board is empty. In Scrum board domain, empty board is not a special condition of any kind and does not trigger any special behaviors or conditions.
So, did we just waste our time here?
Negative learning
Let's hold our horses for a minute before deleting this spec and try to write this spec in a Given-When-Then form:
GIVEN nothing
WHEN a new scrum board is created
THEN it should be empty
The Given-When-Then form has this nice capability that you can discover many things by simply negating what you already have (by the way, this is not the only way to proceed, but this is the one I want to show you). We can be smart about it, but for now, I suggest we take the brute force approach - we'll go through each part of the behavior description and try to negate everything we can. We expect to run into some VERY obvious conclusions as well as find some jewels.
Assumptions: Given nothing
Here, we can make only one negation in the following form:
GIVEN NOT nothing
What is "not nothing"? It's "something" :-). What can we learn from it? Not much. In fact, most of the app logic we're going to write will rely on assumption that "there is something". Nothing valuable, let's move on
Trigger: When a new scrum board is created
Let's start with negating the second part (the order doesn't matter, it's just that this part is more obvious):
WHEN a new scrum board is NOT created
What does it mean that the scrum board is not created? It's the central abstraction of our domain, so what's the point in not creating it? This may lead to a question: Is a scrum board the right abstraction? Maybe it would be more valuable to look at it as "a process", not "a place"? I such case, a better abstraction would be "sprint" (which is represented by the board on the GUI level), not board. Let's assume we decided to leave the "board" abstraction (although this thought is so valuable, that I'd probably add it to my TODO list to be able to revise my decision as the development goes on).
A nice thought from such a stupid negation, don't you think? Ok, let's dig further by negating the first part:
WHEN a NOT new scrum board is created
What does it mean "NOT a new board"? I means "an old board". Wait a minute, that's INTERESTING! Let's ask further question: what does it mean to "create an old board"? It rings a bell - it's about "loading an old board". Wait a minute! This is a perfectly valid use case, because no user would like the board to disappear when they turn off the app! As this is not the core functionality, while not letting our self esteem and enthusiasm about this discovery eat us, we add it to the TODO list for later and proceed with the next negation.
Outcome: Then it should be empty
Again, there is only one negation we can perform:
THEN it should NOT be empty
We thought knowing that the board is empty doesn't help the app at all. But what about us? Does it help us learn anything? Let's check it out by asking further question: when is the board not empty? The answer: when there is at least one task added. Yes, we discovered the next use case - adding a task. This looks like the next core functionality element we need!
Let's document this discovery with a Given-When-Then form:
GIVEN an empty board
WHEN a new task is added to it
THEN it should not be empty
And an executable specification:
[Test] public void ShouldNotBeCleanWhenTaskIsAdded() { var board = new ScrumBoard(); var task = Any.InstanceOf<Task>(); board.AddNew(task); Assert.IsFalse (board.IsEmpty); }
Now THAT'S a discovery! Let's quickly recap what we've learned here: we've learned that there is an abstraction called "Task" that can be added to the board by invoking the "AddNew()" method (which needs to be implemented: another item for the TODO list!). We've also got another Given-When-Then behavior description that we can analyze the same way as the first one. Shhh... I'll tell you a secret: until now we've been proceeding asking question "What if not?". Another useful question to ask is "What else?", and we can apply it to the WHEN part by asking "What else happens when we add a task?". But that's another story.
Summary
What I tried to show you is how TDD can be often used to inform analysis. I've also tried to show how potentially pointless specs can help us learn more on our domain or ask the right questions about the domain. I'm not trying to say that TDD is a substitute for other analysis methods, such as commonality-variability analysis, but it sure adds much to your current toolset.
Ok, that's it, time for a walk :-). Bye!