to have a serious conversation about *how* to test, we have to start by considering *what* to test. as a microtesting TDD’er, what i want to test is *our* *logic*.
the money premise of TDD reminds us we’re in this for the money, that is, the primary purpose of TDD is to enable us to ship more value faster.
deciding what is “value” is not a TDD task. the larger agility certainly asks us to make hard choices about value, but by the time we TDD those (always temporary & dynamic) decisions have already been made.
what i am saying is bold, and it will not surprise me if it surprises you, even if you already are a TDD advocate.
i am saying that there is no “change this code in a way that requires branching” task with scope bigger than half a day — in its entirety, in perpetuity — that we won’t do faster using microtested TDD, assuming you a) know how and b) have the TDD tool at the beginning. it absolutely does not matter *why* you or your team or your org has decided you need to change code in a way that involves branching. that matters to you, your team, or your org. it doesn’t matter to the assertion i am making.
more features? TDD can do that faster. fewer bugs? TDD can do that faster. get this thing to alpha so we can see if anyone’s dumb enough to buy it? TDD can do that faster. see if complex variant X is performant? TDD can do that faster.
it’s not because TDD is the end-all and be-all of ll possible situations. it’s because TDD is the fastest current known technique for changing code in a branchful way, and all of those depend on changing code in a branchful way.
i reiterate: i am assuming a) you know how to do it, b) you have your rig ready to go at the start. those certainly aren’t valid assumptions to make everywhere all the time. but why would you bother learning or setting up if you weren’t gonna get a payoff?
the payoff — i *have* theory galore, but i am not coming from theory here but from practice as a hardcore working geek — is that *any* code changing that requires even modest logic goes faster with TDD. we don’t care about what/why the value is in this code-changing context. we only care that it’s code-changing in a situation that requires us to combine logic.
another way to say this: i am interested in TDD’ing any code we wrote that has a local mccabe complexity > 1. if you don’t know what mccabe complexity is, there’s a thing called google, but the short phrase is “number of possible paths through the code”.
many of us start a new language by learning how to print “hello world” to the screen. if it’s a console program, that’s a one-liner. in its entirety, it calls a library that is built in to your programming language. it has a local mccabe complexity of 1. there’s only one path through it. no branching, no logic, no nothing. it is basically an exercise in typing. i do not wish to test it.
wait. that’s not right. it’s not that i do not wish to test it. it’s that it’s cheaper for me to run the program and see if it says “hello world” using a device i call “my eyes” than it is to write a test, even if i know how to test & i have a rig. and because it has no branches and depends entirely on the system library in nearly every respect, i am happy to use my eyes one time and walk away.
(aside: occasionally we confront situations where *transitive* mccabe complexity becomes important. those situations inevitably involve mutable state, which is one reason we don’t like mutable state.)
now i will run out of steam here in just a minute. but i want to complicate this picture a very tiny amount, and in so doing, we’ll move from just the money premise to the money premise plus the chain premise plus the steering premise. spoze i sometimes want to print “hello world” and sometimes want to print “hi mom!”. i have to *branch*. i have a condition — let’s say i have another library call coin(), like print, that returns heads or tails.
if( coin ) print "hello world";
else print "hi mom!";
the first thing you note, the mccabe has gone to two. there are now two paths through the code. second thing you note, we have *our* *logic* in the game now.
it is at this point that you realize you haven’t the slightest ability to easily capture the output of those print statements. (you have encountered, in fact, your first awkward collaboration.) enter the chain premise. we stop testing the *app* and test a part of the app. we don’t really want to test print or coin at all. we know they work. (if they don’t work, we have bigger problems.)
we want to test our logic. we want to be sure we do the right thing on heads and the right thing on tails. so what if we just test our logic, and not test the whole thing? uhmmmm, nothing. we got nothing. the fact of the matter is that those damned print statements are in there screwing up our ability to test just our logic.
enter the steering premise. what if those print statements were *not* intertwined with our logic that we want to test? then we could use the chain premise to test just *our* *logic*, and to hell with testing a system library call.
the only way to do that is to change our code. rearrange it so we first decide what to print — our logic — and then print it, a thing we trust works and can eyeball cheaply.
to test that logic separately we need it separate, in a function.
String greetings( boolean flip )
if(flip) return "hello world";
else return "hi mom!";
the tests we write are two, one for each local mccabe path through greeting.
we have *steered* our design. we took into account what we wanted to test — *our* *logic* — and we changed our code so that we could test it easily.
we started by considering what to microtest. we want to test *our* *logic*. we don’t want to test the app. we don’t want to test the library. we want to test the parts where we write code that branches, and we want to do it cheaply enough that it makes us ship value faster.
the money premise reminds us this is more value faster. the chain premise tells us to test parts rather than the whole. the steering premise tells us to change our code to make that possible. there are other problems. and we have answers for them, too. but to start TDD, start there. you will go a very long way. money, chain, steering.