In microtest TDD, we describe collaborations as "awkward" or "graceful". The distinction is critical to understanding how the Steering premise and the Pieces premise work together to make TDD a productivity tool. Let’s dig in.
Here in the states, it’s Black History month. I urge my friends and followers to pay attention: American history and Black history are deeply & painfully intertwined, and we won’t move on until we go through.
Black Lives Matter.
We talked the other day about understanding & manipulating dependency in microtest TDD. The awkward/graceful distinction is at the heart of this. It can be a long journey to get it all, but it starts as soon as you take TDD for a spin in your day job.
To get into this, let’s take two classes most of us are familiar with: String and File. (I’m thinking in Java, but I suspect much of what we see will apply in most languages.)
String is graceful.
File is awkward.
Let’s see what that means.
Spoze we have to TDD new functionality, code that takes the bewildering variety of ways people enter phone numbers into text fields, and tries to parse and reformat it into a valid E.164 international phone # for the most common variants. It’s String in and String out.
TDD-ing such functionality is straightforward. Each test calls our code with a String, and asserts against the String it returns, and all we gotta do is keep stepping, one variant at a time, from red to green.
Spoze we have to TDD new functionality that transforms an incoming file tree with files that cross-link to each other, into a filtered output tree. Think, hmmm, scanning your java source to build a tree that includes only files that transitively depend on an arbitrary import.
TDD-ing such functionality is going to involve considerable nether-region discomfort. Every test case must start by rigging up a file tree. Every case must end by asserting against the output file tree. And the variants are rich enough there’s a lot of these tests to write.
This is the difference between graceful & awkward. With graceful collaborators, the test-effort recedes into the background. You get to work on the problem, not the tests. With awkward collaborators, otoh, the problem is easier to solve than the test is to write.
A couple of points: 1) It’s not the complexity, of either the problem or the collaborators. 2) There are lots of ways an awkward collaborator can be awkward.
The two problems we described are roughly equal in difficulty. Both involve basic stuff every first-year pro knows how to go about doing, with possibly a little time spent surfing the web for ideas. So problem complexity isn’t what makes things awkward.
And File and String, the collaborators in question, are also roughly equal in complexity. They’re both "front" classes, hiding several other classes under the hood. They both handle bizarre complications that a first-year pro has very likely never even dreamt of.
There are several kinds of awkwardness. To name just three:
- awkward setup,
- awkward runtime,
- awkward inspection.
File has all three of these. 🙂
Setup: You’ve got a few choices, but they all kinda suck. A Java File can’t be anything that isn’t a physical File on a drive. You could make each test generate the file tree on the fly. You could pre-generate them and have them in your test/resources folder. That’s about it.
Runtime: The physical drive on your ‘puter is the slowest thing on your box. To get slower than that, you’d have to use the internet. 🙂 And again, our ways around this are limited, basically, to "use a RAM drive", a non-trivial setup. Your tests will run in seconds, too long.
Inspection: And when you’re done? You have to go walk that output file tree. It’s the same output as setting things up, but in reverse.
These awkwardnesses, and others, are different from one another as causes, but their effect is the same in each case: they make us not want to write, read, run, or debug tests. They make TDD a chore, an annoying box we check off to get to the good part, solving problems.
The awkward are always with us, to coin a phrase.
We can’t write apps that never use the filesystem, or talk to a database, or hit a web API. We just can’t. So if TDD is going to work, we’re going to have to have recipes, patterns, tricks, some way or ways to handle awkwards.
We do. There are several of them, and we use different ones in different contexts. But though the answers vary in detail, we can actually summarize their effect in one sentence: we re-design to get the core of our problem to be handled by a clump of graceful collaborators.
That’s it. That’s the whole thing. We recognize that some of our pieces are graceful, and some of them are awkward, and we push the awkward ones out toward the boundary of our design, and keep the graceful ones inside.
Consider our Java import problem.
What do I need to do to know which files to copy out? I need a bunch of little dependency chunks: one filename that is the dependent, and a bunch of file names that are the dependencies.
Filenames, my friends, are Strings, and that little dependency class is just a String and a list of Strings. We can can TDD the core of our work w/no Files at all.
What do I need to create those dependency guys? Well, I need the contents of the dependent file, which I scan and extract import statements from. import statements are Strings. And Java source files are just Streams of characters. Again, we can TDD that w/o using File.
Now. On the input side, I still need something to walk the tree. Turns out Java comes with that. We have to write a FileVisitor class from an interface. And yes, we have to test it, and we have to test it with real files. But all it does is send streams off to be parsed.
And on the output side? Well, we wind up with a list of files to copy from one part of the drive to the other. And again, yes, we have to TDD that. But again, that’s all it does.
Those last two pieces need tests, but they don’t need very many, because we isolated their operation. One only turns a file tree into streams. One only copies a list of files. Still mildly awkward, but far less so.
The results of all this: we get to work on our problem, using TDD, and feeling very little pain as a result.
And we did this by invoking the Pieces premise: our desire to prove that the individual pieces of our app do what we wanted them to, and the Steering premise, our willingness to change our design to account for tests & testability.
Now, this is just one way of doing this, and after all, it’s not an incredibly intricate problem. But I hope it gives you a feel for it. There are several variant ways to get a clump of graceful’s with a crusty border of awkwards. And it’s not hard, but you gotta look for it.
There are awkward collaborations and graceful ones. To get good at microtest TDD, we need to know the difference, and we need to learn how to use the Pieces & Steering premises to get graceful clumps of code with a few awkward classes wrapping them in a border.
If you love the GeePaw Podcast, consider a monthly donation to help keep the content flowing. You can also subscribe to get weekly posts sent straight to your inbox. And to get more involved in the conversation, jump into the Camerata and start talking to other like-minded Change-Harvesters today.