I spend considerable effort making it possible not only to implement a test I want, but to make that test easy to read, to write, to run, and to debug.
I’ve talked a lot about five premises of TDD.
The money premise, the steering premise, and the chaining premise all get involved when we come to the coding of the tests. We do TDD to ship more value faster, the money premise. What makes it able to do that? The cost of the tests must be small. The benefit they provide must be big.
The chaining premise says it’s usually cheaper to test A-B, then B-C, then C-D, than it is to test A-B-C-D. That’s why we use fakes, aka test doubles, aka (improperly and colloquially) mocks.
The steering premise says that those tests and the general testability of our code using them is a first-class design citizen: that we will change our design to account for them.
In the dayjob, I’ve just spent almost ten days alternating between refactoring production code and creating a new way to express a certain class of tests that are frequently written against it.
These tests are poking at the heart of the system: the gradual construction and dynamic alteration of a set of 6-deep tree-shaped data elements. Each of the 6 levels is different-by-type. That is As are on level 1, Bs on level 2, and so on, until we finally get to the leafs.
The problem I encountered fits in one sentence: "I can’t read these tests quickly, and I can’t write more of them quickly." There are already perhaps 200 of these tests. My customer, bless her evil litte heart, threw a story at me that will maybe another forty or so. It’s unlikely to be the last time this happens, too.
Those 200 tests didn’t start out insanely complicated. The trees didn’t start out as 6-trees, either. But as we iteratively solved the customer’s problem, they grew more and more rich.
There’ll be another muse soon, one about tacit vs explicit relationships in code. It’s relevant here, because its part of how the hard-to-grok tests continued to pass.
The long & the short of it, those tests cheated, by hooking up data in "just-legal-enough-to-get-through-the-test" ways. That hooked-up data created 6-trees that were not possible in production, but not actually prevented, because of the tacit relationships being unenforced.
Anyway, as I say, I’ve spent days and days on this. I’ve converted about thirty of the old tests to the tighter expression, which makes me think the rest can move to it, too, tho not all at once.
Here’s the thing: I feel great and I feel awful.
I feel great because it was a nasty problem and I feel I’ve cracked it. It was heavy-duty geekery finding ways to do it that kept us green. Along the way I refactored hundreds of lines of existing code, essentially replacing tacit w/explicit.
I feel awful because, like every geek on earth, what I want to do in life is knock my customer dead with my geeky brilliance. But I’ve been unable to ship a story for two weeks. That’s a staggering amount of time for me to go without pleasing a customer.
And, I still can’t quite ship it, though I believe the guts of the solution are there, and now it’s just TDD’ing the pieces into place.
Anyway, it’s certainly been an educational two weeks, absolutely fraught with lessons I want to share with you in these tips. And this is the first one: make and keep the tests cheap. It’s not ready to ship just because I got to green. It’s not ready to ship just because I refactored the production code.
Ya gotta refactor the tests.
In TDD, we live or die by the quality of our tests. Can we read them? Can we write them? Can we run them? Can we debug them? If the answer to any of these questions involves rocket science, we start to lose much of the value TDD brings to the table.
So, the dayjob is done for me for today. (Writing muses is part of the nightjob.) I’m gonna go play a game or read my book or eat some ice cream and not think about code for twelve hours.
Have a lovely night or day or dawn or dusk or whatever it is for you!