TDD Pro-Tip: remember who works for who, and shape your tools to your hand, not your hand to your tools.
(Btw, these tips are in no particular order. If you want to know the truth, i’m musing them as I do them. People always know more than they can say, and i’m no exception, but i’m digging at what I do over and over, and sharing it.)
One difference I see over and over between noobs and olbs is the amount of time that olbs invest in making their environment — software tools & source-base & hardware alike — do exactly what they want.
Let’s call all of that, the s/w, the h/w, the test-base, the shipping-base, the whole thing, let’s call it the environment. Olbs, then, feel that the environment works for them. When an aspect of the environment is annoying, olbs stop what they’re doing and change the environment until it isn’t annoying. Noobs don’t do this with that kind of authority and confidence.
Two sketches for examples, both test-related, one simple and one mega, both of which I have/am doing on my current gig.
The simple sketch: the code that correctly ascertains that two modest tree-shaped structures are identical is about six lines long. I need to ascertain that across quite a few tests. As soon as I get it working the first time, I see that it’s quite generic except for the two structures to be compared and one extra parameter that guides part of their comparison. But those three args are embedded in non-obvious places in that six-line snippet.
Without even thinking about it, I extract that six-line snippet into a method parameterized by the three arguments. Why do I do that? It’s because that code works for me, and I find it hard to grasp quickly in its native state.
If I worked for the code, i’d blame me. Y u so stupid & ugly u can’t quickly pick out the three important differences between these six lines and those six lines? But the code works for me. If I have a problem, the code has to change, not vice-versa.
Here, the code is the environment. Sometimes it’s the tools. I was working perl not long ago — the ’80s yet live — and the test facility there is procedural, verbose, and doesn’t take complete perfect control. So what did I do? Took a couple of days and improved it.
That tool works for me, and I use it every day all day long. I’ll give it a little while to see if the blisters on my hand are just a toughening thing. But if they’re not, the tool suffers. I don’t suffer.
A mega sketch, then. A great many of us work with complex databases with dozens of interlinked tables, fronted to us via service calls. (true of most modern web-ish dev, but also of much modern backend dev.) like many of us, the base of my environment is just such a beast, where my code is downstream of a whole ton of "not-my-code" data suppliers.
- The upstreams are slow, sometimes unstable, & contain massive amounts of data.
- My app uses the upstreams mostly opaquely, indifferent to actual content.
- Even when the upstream values are actively used, I need only dribbles of it to prove my code works.
So? I build my code so that those upstreams are the barest thinnest interface, and I back that interface with two versions, live and dead.
The live version really does call the database or the service. It really gets whatever random shit is in there. It really reads thousands and thousands of items. The dead version contains a hand-rolled dataset that is miniscule, expressed in code in a DSL, using prototypes (builder), and has just enough in it to demonstrate every variant I need to deal with.
And I hear you thinking: he’s talking about a fake, a test double, a mock.
On the one hand, i’m glad your mind went there so quickly, it proves you’re paying attention. On the other hand, you’re mistaken.
That hand-rolled rigged dataset is in the shipping code, with a feature-toggle that tells the app whether to use it or not. It’s not testing code. (i do use it sometimes in the testbase, but the shipping base depends on it.) why would I do that? Because in actual TDD as opposed to toy TDD, I am solving problems I have never solved before. I don’t have a testing framework that lets me poke at them — yet.
I need to run the whole app dozens of times a day to find my way in to that code. A lot of it is stuff I will never bother to test. Once the layout pleases me aethetically, both in the code and on the screen, I will never lock it down using tests.
Some of that work i’d like to test, but as I say, I have no idea yet how to do so. I need to run the app to get clues, and I need to run the app to back up my refactoring-to-testability push.
(and some? Some of that work I will never figure out how to test in a way that is efficient and effective. In javafx, there are acceptance-test frameworks galore, but they’re neither efficient nor effective.) the point is, I need to run the app dozens, hundreds, of times every day. And with unstable, slow, and gigantic upstreams, doing that is hell.
Guess what, tho. I don’t work for the code. The code works for me. Investing a few days to get the dead datasets working will save me and other, future, devs thousands of hours of just sitting there waiting to see what happens.
We’ve mostly seen the money premise and the steering premise, and they’re certainly being invoked here. It’s a waste of money for me to sit and watch a slow-loading or unstable upstream, that’s the money premise.
And the steering premise says we change designs for testability — but note that here the testability I want isn’t micro-testability, it’s human-testability. I want to run the app a whole bunch to figure out what I want, not a part of the app to prove it does what I want.
And how/when do I decide enough is enough, and that the tool has to change shapes because my hand isn’t gonna change shapes? Ahhh, well. We encounter the judgment premise.
The judgment premise: in writing software we are permanently, ineluctably, irremediably, irrevocably, and happily dependent on individual developers using their individual judgment.
The activity i’m talking about here, remembering who works for who, and to shape the tool to the hand and not the hand to the tool, is absolutely riddled with me making complex nuanced judgments.
I sometimes put it another way: "become sullen and resentful when something doesn’t work well for you." what i’m saying there is that your discomfort and what you do about it is an absolutely central topic in mastering geekery.