TDD Pro-Tip: What we TDD most rigorously and faithfully is our branching logic. This is the insight one needs to start gaining TDD’s massive productivity benefit.
All three terms are important, "our", "branching", and "logic", so let’s take them one at a time.
Remember as we forge in to this: there is no free lunch, but if there were a free lunch, writing a test ain’t it. Every test we write costs something and benefits something. We need that benefit to outweigh that cost, as often as it is possible for us to predict in advance.
We all wish that 100% testability were possible: 100% of the code, 100% of the combinatorics, of the features, of the UI, the UX, the database, the http transport, the . . . well, it’s a long list precisely to make my point.
100% tested is probably not possible even in theory, and it’s certainly not affordable in practice. So we will choose which tests we write.
Why is "ours" a baseline criterion? Why does a TDD’er spend most of her time and energy testing our code? The answer is twofold: 1) it’s where the bugs are most likely to be found, and 2) it’s where the bugs are most likely to be fixable by us.
Modern professional code depends on a staggering quantity of other code. Every line of pure console-oriented java one writes, for instance, is dependent on dozens of layers of other software, from the compiler, to the bytecode engine, to o/s, to the assembler, to microcode.
In fact, when our code is alive in an app, it is almost certainly the smallest portion of all the code that’s running at that time.
Nevertheless, we are by far the most likely source of bugs in our running app.
This is not because everyone else is smart and we’re kinda stupid, albeit very attractive in our own freaky way. It’s because most of that software we’re relying on is in use by tens of thousands, maybe more, of other people.
It’s not that it never had any dumb bugs, it’s that a huge number of users has found a great many of them a long time ago. (Notice that this distinction shades away when we’re using brand new stuff. Never take anything I say as suggesting you won’t need personal judgment.)
Secondly, beyond just playing the odds that strongly suggest most bugs are my bugs, is the fact that a great deal of that code, if buggy, is something I can’t do anything about anyway.
Now, again, there are shadings: all long-term geeks have encountered bugs in code below their level that they could do something about: that’s what workarounds are. But even here, the pre-use of all that other software is a great assist, because that’s what the internet is for.
What about "branching"? Shouldn’t we also be testing the straight sequences of our code, regardless of whether they contain multiple paths?
Sure, of course, we "should" be testing everything, remember? But when I have to shave away tests to make value — I am paid to make value — I attend far more to the places in my code where a chunk does either one thing or another.
So, why? Because, again, the bugs I create are far more likely to derive from the splitting of paths in my code.
There are basically two kinds of problem here: 1) I got the split wrong. That is, the first path is taken sometimes when the second path should have been. 2) I got the balance wrong: the first path does something that the second path should also do, or vice-versa, or does not.
Straight sequences, with no branching, on the other hand, tend more to a "if it works one time it will work forever" stance.
(For the close and argumentative reader: a) if a sequence works one time and doesn’t work the rest of the time, you’ve just discovered a place where you have to split the path, making my point for me. b) I love you, keep the faith, sister.)
And finally, our third term, "logic", this meaning "choice-making" as opposed to "calculation". Why do I tend to focus my TDD more on the former than the latter?
It’s because the overwhelming majority of the math that programmer’s do is 100% domain-safe. That is, if the inputs are legal in the domain, the output is guaranteed to be right, far more rigorously than with choice-making logic.
How, one might ask, do I deal with situations where the inputs are not domain-safe? Ahhhh, well, I use logic and branching to determine they’re not, and don’t throw divide-by-zero errors. 🙂
So you see, I get the biggest bang for my TDD buck by using a fisheye lens. The great big magnified part of the picture is "our branching logic". The more peripheral, further away, less focused part is anything that isn’t ours, isn’t branching, or isn’t logic.
The money premise says, "We’re in this for the money". Why I wrote it that way, is to point out that in software development, everything I do is about shipping more value faster. When I TDD, I do it because it helps me do that.