Part of 2 in the series
First short TDD session pushed. I said I’d start yesterday, but instead I played a computer game most of the day.
This was short and sweet, so there’s just three simple points of interest, one noticed infrastructure problem, then I’ll move on to the next part.
POI #1: This was textbook TDD, as most toy problems are. Think of a test, write a test, fail a test, pass a test, design it all. No shockers or embarrassing pauses or staring up at the ceiling.
POI #2: A technique I use sometimes is a triangulate test. It’s a test I write explicitly to force a change to the code without cleverly figuring out some other way to do it.
In this case, I had "accepts string value" to test that ouput[key]=value can then get output[key] and it has that value. I then wrote "triangulate accepts string value" which did it with another actual literal for value. This isn’t common, and it isn’t a keeper test. It’s a test I wrote to move the ball. Even when I start to describe the second test, a pair, or you, will immediately force me to write it a different way. And that’s correct.
I use TDD operationally, that is, without denying that the tests — the artifacts — have value, it’s the TDD’ing — the operation — that brings me the biggest bang for the buck, not the tests themselves.
I wanted to force myself to write a particular line of code, so I wrote a test to do that. The test isn’t righteous, really, I’ll re-write it soon. And this is important, and it’s a segue to the third point. I write tests against what I want to do next, not against an endpoint.
POI #3: All this thing is right now is a thin wrapper around a map<string,string>, with exactly two differences from that. 1) keys can’t contain ".", and 2) you can’t re-assign keys. Why? I mean, not why the differences, but why is it so "not the expected real thing"?
Well, I mean, c’mon now, this is pathetic. It doesn’t do any of the things except those two guard-clauses, that the "real" TsdOutput will do. Everything else any old map would do, and in fact these are all implemented under the hood using a map.
It is because, at this time, that’s all the tests have required me to do. If, and believe me that’s really "when", I want it to do more, I’ll write more tests that make it do more. One of the most difficult ideas for TDD-noobs to get their head around: TDD is temporal. It takes place over time. This pathetic code exists because it passes these pathetic tests, and it is absolutly shippable until there are more or different tests forcing it to change.
The single most common approach to TDD by a noob looks like this: 1) Take a toy problem. 2) Solve it, entirely, in your head. 3) Write some tests to drive that mental solution into place.
That’s a good start. It’s how nearly all of us started. But it breaks down pretty quickly. If the problem isn’t a toy. If the solution isn’t doable in your head. If the solution in your head isn’t complete. If the solution in your head solves problems no one wanted solved.
In that approach, it’s like the solution in your head is the finish-line, and all the tests you write are just tests against that finish-line. This is actually making the same mistake writ small that so many projects write large: they aim at the end, and not at the next. Use the tests to describe a problem. Use the code to describe a solution. When the problem grows or changes, grow or change the tests, then grow or change the code.
And now return to POI #2, that triangulation test. Hey, wait a minute. Didn’t I write a test to force my mental solution, like I just said we don’t do?
Mmmmmm. Yes. The force is strong in this one. It was a micro micro micro case, to be sure, but it sure as hell was a case, wasn’t it? How do we account for this? More important, what do we do about it?
Well, one thing to do about it is what I did: I shrugged, accepted that it was not a righteous test, and kept moving, knowing that I will soon just sit with my tests and code and design them both well, and the unrighteous test will pass.
There’s a sense of change deeply embedded in the TDD mindset, and indeed in all of the practices most central to this community: "It will change, soon, and that’s normal and good, even perhaps best."
The triangulate test will be renamed and made better. The fact that I pushed my mental solution a little faster than I pushed my test is just a fact of the world, of me, of the temporal arc of the development. I did it because I guessed it would make me ship more value faster, and that’s the reason, I call it "The Money Premise", that I use TDD ever at all. We’re not in this for the purity.
Wow. One thirty-minute TDD session and I hadda go write a novel about it. I spoze I better get back to work. I’ll report on the next session here in another hour or two.
Hope you’re having a lovely Sunday!
Oh! I forgot the infrastructure problem. Though my tests run in IntelliJ, they don’t seem to be getting found and run by the command-line gradle. I’m gonna practice "not worrying" on that for the moment. 🙂