Today it’s microtest TDD’s Value Premise: TDD ships more value faster when that value depends on changing our branching logic safely & quickly. Let’s dig in.
Before we start: geekery continues to seem largely irrelevant to me. 1 in 900 Black Americans have died of Covid. They’re still shooting unarmed men & boys in the street.
Black lives matter. Please help however you can. Friends, stay safe, stay strong, stay angry, stay kind.
I am frequently presented with declarations that TDD is fine for plebs like me, but useless for software of type X. I’ve heard every variety of app type or coding specialty or domain substituted for that X.
In other parts of the forest, I hear that the purpose of TDD is high quality, and if you don’t care about that, you don’t need TDD. Or that it’s for satisfying personal or professional ethics, and if you don’t care about that you don’t need TDD and you’re a bad person.
The Value premise says that TDD is an instrument for productivity, and its effectiveness depends on how much of our work requires us to change our branching logic, which it lets us do with greater speed and lower risk than non-TDD methods.
Let’s start with that curious phrase, "our branching logic". It’s not chosen casually. It comes from 20 years of answering Hamlet: "to TDD or not to TDD? That is the question."
Here’s a little chunk of code. It’s Kotlin, it’s a view class using TornadoFx.
Tho I am a pretty serious TDD’er, there are no tests for it, nor will there be.
Why not? Because it’s not worth TDD’ing. Test-driving that class would cost me a lot and benefit me very little. The reasons this are true: 1) top-level, 2) all-or-none, 3) visual, 4) no decision-making. 5) GUI glue code. Let me quickly explain each of those points.
Top-level: This is the highest level class in the app. None of my code depends on this class, all of my code is depended on by this class.
All-or-none: Like all classes, this one’s going to either work or not work. But unlike most classes, the difference will not be subtle or partial, it will be dramatic and complete. It’s all or nothing with this guy, and see the next point, visuality, for the all.
Visuality: This class is laying out a simple and fixed screen. My eyes can test it far more quickly than any code I write. Every visual component there has dozens of properties. Which ones are important to test? They’re all important. And they’re all defaulted by the builder.
No decision-making: This class is a straight line with zero decisions made. It doesn’t behave differently given different inputs. It does the exact same thing every time.
(Important: That’s not an accident, it was designed that way, see the Steering premise.)
GUI Glue: this is not technically a data declaration, it’s a bunch of calls to a slick Builder pattern from TornadoFx. But it might just as well be. And none of it is our code except the lines that say "model" in them. Those lines are of course TDD’d elsewhere.
Now here’s another class. Also Kotlin, from a different app.
This class is wrapped quite tightly in tests, and was built using TDD all the way through.
Why? Because it’s very very much worth TDD. 1) deep dependency leaf, 2) separable function, 3) computation galore, 4) decision-making galore, 5) all ours. Again, let’s glance at each of thse.
Deep dependency leaf: The Area class is used by a dozen other classes, and by some of them it’s used dozens of times. It is a deep dependency in the app. If it’s broken, the app is guaranteed to be broken.
Separable function: this is no all-or-none class. I could have the intersection working perfectly and the union or the fill broken. Lots of ways this guy could be "almost right".
Computation: The exact opposite of visuality is in play here: I can code tests around these numbers far more quickly than I can look at the screen results of using them, or for that matter, even study the code to figure out what the numbers should be.
Decision-making: It’s shrouded by Kotlin’s idiom, but there’s a lot of logic in there. The various expressions with logical operators, the usage of min & max. These are all implicit branching logic.
All ours: The only part of this class that isn’t our code is the calls to Integer.min and max. The rest of it is us, all us. If it’s broken, it’s broken because we broke it, and we will be the only people who can fix it, and the best people to determine tht it needs fixing.
So, you can see that the phrase "our branching logic" is a very important part of TDD’s value proposition. When it doesn’t branch, isn’t ours, has no logic, that pushes us away from TDD. The more it has any of those three attributes, the more it pushes us towards TDD.
Let’s unpack "changing quickly & safely" a little.
If I had to pick one bad idea out there dominating the trade, well, I couldn’t. But surely a major candidate: It is commonplace for developers and their leaders both to insist that changing code is rare. This is nonsense.
During development, even in old school one-and-done projects, the code is undergoing non-stop change. It’s not always global, ideally it’s even highly localized. But the complications of modern apps make this a very hard thing to determine statically.
And, as we’ve pointed out, one-and-done projects, where we ship version 1.0 and move on, are becoming the minority of professional software development. We routinely change code that we have already shipped.
Changing code is not a rarity, it’s the actual heart of the job of being a professional developer. This is as true in one-and-done work as in apps like Word or vi or sites like Amazon. (Word for WIndows, the dominant enterprise word-processor, is 30 years old. Vi is older.)
"Change is the only constant" sounds like I’m being faintly mystical, but it’s the simple reality of rolling code for money.
It follows, trivially, that anything that makes change faster or anything that makes change safer would be valuable. TDD does both at the same time.
Executives have various values they want from our work at various times. More features. More cases. Better UX. More performance. More stability. And the Value premise says: since getting all of those involves us rapidly and safely changing code, TDD can deliver on any of them.
I’ve gone on too long. Let me just add one more thought: a caveat.
In this and all my muses, I advocate the heavy usage of TDD. But my TDD may not be what you have seen or heard about TDD. Many ideas that have been popularized about TDD aren’t what actual TDD’ers think or enact.
Successful TDD is a rich and complex skill. It is not a saying or an algorithm, or something one learns on a web page or in a two-day class. It’s not a programming add-on, but a whole different way of understanding how programming works.
TDD is not obvious.
The Value premise doesn’t stand alone, it collaborates with and elaborates the other premises: Steering, Judgment, Pieces, and Correlation.
There are plenty of great teachers out there, and they can help you learn it, but they can’t learn it for you. You gotta do that, you gotta do the learning and the practice.
The Value Premise won’t work for you until you do.
Microtest TDD’s Value Premise: The value of TDD is that it lets us change our own branching logic quickly and safely, and that is why TDD’ers use it.
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.