TDD Pro-Tip:
The first step past beginner-TDD lies in grasping the difference between awkward and graceful collaborations, and learning the myriad contextualized patterns for moving from the former to the latter.
Folks invest a little in TDD, maybe cuz they’re excited, maybe cuz they’re ordered. They read a little. They try an exercise or two. They learn a little about their xUnit options. Maybe they boldly even use TDD to write a method or a class in their day job.
All good stuff — except the dumb-assed ordering of people to do a complex activity without adequate training or support, but that’s a topic for another day — and all, well, beginner stuff.
There’s a lot of text out there about that stuff: tons of exercises, kilo-tons of opinion, some good videos and some bad ones, and so on. But there’s not a whole lot out there for folks who want to move past beginner-dom.
So here we go.
Testing a collaboration is never free. Sometimes, though, it’s dead-cheap. Other times, it’s wildly expensive. And mostly, it’s somewhere in between, neither a no-brainer nor quite rocket-science, but something else.
"Awkward" and "graceful" are my words for describing not so much the endpoints of this expense-spectrum for testing collaborations, as for the relative positions in between.
A pre-understanding: we’re speaking here of satisfying geek concerns: "does this code do what I wanted it to do?" being the primary one. There are other places s/w dev can go wrong, but they’re out of scope today. We’re specifically limiting ourselves to that question.
A second pre-understanding: for any fixed stable requirement, there are an infinite number of turing tapes — programs — that could deliver it. That is, there’s not only more than one way to skin a cat, there are a vast number of ways to do it. (who coined this expression?)
So we’re talking here about various rearrangements of the text of a program fragment and what impact they have on our ability to satisfy us that it does what its author wanted it to do.
"Awkward" means that a given arrangement is closer to the expensive end, and "graceful" means that arrangement is closer to the cheap end. A TDD’er given two such arrangements, if she can see that one is more graceful than the other, will prefer the graceful one.
Let’s concretize that a little, eh? Consider a java program that takes a string on the command line and interprets it as a file name, opens the file, reads its lines, sorts them, and prints them out.
The java.io.File
class is a fairly standard starting point. Grab that filename, stick it in a File, open that File, and read bytes. Do the sort. The System.println(...)
method sits at the other end, and print out the strings when we’re done sorting them.
It would be a simple matter to arrange this program as a single method with just such a sequence of instructions in it. (There’s even a sort(...)
we can use in the middle.) fifty lines or so, and bob’s yer uncle — I’ll spare u the origins of that phrase today — what’s next?
A TDD’er though doesn’t just want "done". That’s a noob thing. Olbs know that there really is very little "done" in software: it lives in a state of perpetual change until the day it stops making its org enough money to stop supporting it.
A TDD’er wants that code to be change-enabled, and believes that microtesting the logic of it is a critical part of that change-enablement. So she’s not gonna arrange it as a single long method, she’s going to break it into pieces.
Why? Because in that arrangement it is quite awkward to test her logic. Basically, she will be forced to use the command line to find out what she wants, and to run the entire program, and to supply it a physical file, and to somehow capture its output, and so on.
And if she wants to be sure she’s done what she thinks she has, she’ll want repeatable tests for a variety of situations — empty files, empty lines, duplicate lines, no trailing delimiter — and so on.
So she rearranges. Java.io.File
must be resolved to a physical file on a drive. The console represented on the other end is slightly better, but still represents difficulties. She sees them both as "awkward". The truth is, they make her want to not test, because it’s tedious, it takes time and effort to set up, extra-source data that has to be managed, and so on.
If she knows herr java, tho, the rearrangement is easy: split the method into pieces. makeReader(...)
takes the cmdline
argument and turns it into a java.io.Reader
. readAndSort(...)
takes a Reader and returns the sorted lines. writeOutput(...)
prints them.
This arrangement leaves us with two tiny pieces, makeReader and writeOutput, and one larger piece, read and sort. The main program is just the three calls. It works exactly the same. It’s just a re-arrangement. So why do it?
We do it because that larger piece in the middle, readAndSort(...)
, can now be passed a Reader, and Readers are easy to set up and pre-load in java, far easier than Files.
They’re so easy that we can do it inline, in JUnit
. Each test method basically takes a long java string, runs it trhough readAndSort(...)
, and asserts on the size and ordering of its result. No files. No clever i/o trapping. Easy-peasy.
Look at that.
That is one pitiful example, isn’t it? I mean, c’mon, it’s a trivial problem, the after’s only slightly easier/smaller/faster than the before, and its miniscule compared to the code we really write for a living.
Is that the dumbest example you’ve ever seen of a technique?
Ummmm, yeah, pretty much.
But even in this primitive case, we see the beginnings of a far richer conception of TDD than most of the beginner exercises and frankly at least 3/4 of the internet verbiage about TDD. Let’s enumerate what we’re seeing.
- We see that "awkward" and "graceful" interactions are respectively more and less expensive to test.
- We see that straightforward largely typographical rearrangements of working code can alter these values from more awkward to more graceful. (and vice-versa, one supposes.)
- We see that we can readily go from having one file of shipping code and X files of tests, to just two files, one of shipping code, one of testing code.
- We see the single most common pattern for dealing with awkwardness: splitting methods so that awkward parts are small and boring and graceful parts are larger and interesting.
- And off in the distance, we can imagine the outline of the second most common pattern for dealing with awkwardness: pushing it to the rim of our code.
We’ve used "awkward bookends", wrapped around graceful guts.
When you get past beginner-TDD, learning the testkit, learning to take tiny steps, learning to to use tests to push desired code-changes, the next area of study you will find is about the awkward-graceful spectrum, and the many patterns we have for moving code around on it.
If tests were free, we’d all have perfect tests all the time. No one serious doubts the absolute value of having such tests laying around, what they doubt is the relative value — cost vs. Benefit.
There’s nothing we can do to make tests free. But we can make them cheaper.
We can even very often make tests cheaper cheaply.
All it takes to start is to recognize what makes some code awkward to test and other code graceful. All it takes to finish is — well — practice practice practice.
When you’re ready to get serious about using TDD in your day job, start here: What is graceful, what is awkward?
It leads on for a while:
Can I move between them? Can I learn to anticipate when my code is going to be awkward and resist it? Can I take old code and "de-awkward" it?