TDD does not have to be a rigid process to have value
I like the idea of TDD, but some of the articles and blog posts I’ve read about it feel a little too much like they’re saying “Don’t question our cult! Do what I say!” to me. When proponents of TDD push the “You absolutely have to do it this way or you’re doing it wrong!” viewpoint on me, it really turns me off to the idea. That’s not really unique to TDD. I really hate the “Big Name X said to do it that way, so we should!” mentality and I find it a fairly ineffective way to convince someone that a technology or process has merit. It’s especially frustrating when I don’t think that the author particularly meant for their guidelines to be the Inarguable Truth And Only Way Things Can Be Done, but others who quote them treat them that way.
One of the assigned readings for the TDD study group I’m in was Uncle Bob’s Three Rules of TDD . I was not a fan of this blog post, because it’s fuel for exactly the sort of thing I describe above. Whether or not Uncle Bob really believes his “laws” are immutable (later posts by him give a more reasoned view), I’ve seen this kind of dogmatic approach argued in the past when discussions of TDD came up, with the impression being given that if you don’t do it how it says, you’re doing it wrong. A short training session I was in with David Laribee when he visited SEP in 2010 gave me this same sort of impression (whether he intended it or not). I originally went into the TDD training wary that the DevJam speaker, Kurt Christiansen (apologies if I’ve butchered the spelling of his name) was going to be of this mindset, but I was pleased to see he had a much more practicable approach. The narrow view insists you work in absolutely the smallest chunk possible and basically says you’re wrong if you’re doing anything larger. The more practical view is that you can dial up and down your granularity and speed as best fits the complexity of what you’re working on.
So now, if anyone throws some Big Name blog post at me telling me to do TDD one way, I can throw back some quotes from Kurt: “Don’t be dogmatic about TDD” and “TDD doesn’t mean you have to do the stupidest thing possible”. The rules from UncleBob bother me, because it prescribes a very rigid approach, and seems to be encouraging you to do the stupidest thing possible. Laribee’s talk didn’t just subtly encourage doing the stupidest thing possible… he actually suggested pairing with someone who maliciously does the stupidest thing possible when implementing in an attempt to make your test fail. Good for catching the occasional edge case? Sure! Necessary to catch the edge cases? Probably not. Something I want to do all day long for TDD, regardless of how complex the code I’m working on is? Definitely not!
Uncle Bob says:
“You are not allowed to write any production code unless it is to make a failing unit test pass.”
This seems simplistic and unrealistic. Writing tests for the layout of UI controls, for example, doesn’t make much sense. I mean, the general idea here is right. If possible, you want to test everything you write, and you want to write the tests first. You don’t want to implement ahead of your tests. You want tests to drive the design. You want tests to make it easy to refactor. You want tests to prove what you’re doing actually works and you haven’t just wasted hours on a solution that isn’t going anywhere. But he doesn’t list any exceptions to his rule, and the rest of his blog post just sort of nails the point down further. In the comments, he even considers a 4th law, “There is no such thing as untestable code”. That’s not very practical, and he’s not giving much wiggle room here. Though he does finally address this in a later blog post, which I find to be a much better discussion of TDD.
“You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.”
Really? This is the part that gets me, because it seems to encourage working in the stupidest way possible. I shouldn’t be allowed to think 30 seconds into the future rather than 5 seconds into the future? I’m not saying I’m going to go start writing gobs of code, but if my unit test is going to be 3 whole lines long, can we assume my brain can hold enough information to write all 3 of those lines at once? Am I getting any real benefit out of working in such an awkward manner simply because some TDD guideline says I should? I’m still going to write one test, then make that one test pass. I’m not going out on a limb and doing anything crazy complex. I’m not writing a method and then going in and backfilling 6 tests cases for it. I’m not talking about writing a 30 line test, then going back and trying to make it all work. I’m just talking about working in a way that feels right and makes sense, rather than forcing myself to take the absolute smallest baby step possible.
He goes on to say:
“If you think about this you will realize that you simply cannot write very much code at all without compiling and executing something. Indeed, this is really the point. In everything we do, whether writing tests, writing production code, or refactoring, we keep the system executing at all times. The time between running tests is on the order of seconds, or minutes. Even 10 minutes is too long.”
“Now most programmers, when they first hear about this technique, think: “This is stupid!” “It’s going to slow me down, it’s a waste of time and effort, It will keep me from thinking, it will keep me from designing, it will just break my flow.” However, think about what would happen if you walked in a room full of people working this way. Pick any random person at any random time. A minute ago, all their code worked.”
“Let me repeat that: A minute ago all their code worked! And it doesn’t matter who you pick, and it doesn’t matter when you pick. A minute ago all their code worked!”
I agree that you don’t want to go too long without running tests, but he and I have very different definitions of what “too long” is. Am I really getting value out of writing the absolute smallest bit of code possible, likely one line at a time? Do I have to write one line of my unit test, watch it not compile, stub out the one empty method that makes it compile, compile it (as proof that it does, the equivalent of “making the test green”), write the next uncompiling line, etc? Do my “tests that passed a minute ago” which don’t actually test anything other than the fact that my stub compiles actually provide value to the project? Is the fact that they pass, in itself, something to be proud of? Am I really missing out the TDD experience if I take larger steps?
I don’t think so. The important aspect of TDD to me is that it helps drive your design and also makes sure you don’t get too far down a path of untested implementation. The value isn’t being able to brag that my tests all passed 1 minute ago while yours passed a whole 5 or even 10 minutes ago. Yes, we want to work in small chunks. We want, as much as possible, for our implementation code to have tests behind it. But let’s be reasonable… you don’t have to operate at the most granular level possible to still get value from TDD.
So if someone comes at you with “no, you have to take smaller steps, Uncle Bob says…”, you can just throw your own names and quotes back at them, since both Kent Beck’s book Test Driven Development: By Example and our DevJam TDD trainer said that the idea of TDD is that you CAN work in tiny little baby steps, not that you HAVE to work in baby steps. If you feel you can work in larger chunks, go ahead and do it. You’re still going to be writing tests and making them pass, but you don’t have to go one line at a time. Go at a speed that feels comfortable and once you run into a problem, dial it back and work in smaller chunks until you’ve got it sorted out. I like this idea, and it seems like a more realistic way to work. It allows me to test first and let the tests drive the design without feeling like I’m going out of my way to to slow myself down.
As we move forward with TDD, I think we definitely need to be open to allowing people to figure out what works for them, rather than blindly demanding that they do whatever the latest blog post or book says. Should they read and consider those books and blog posts? Definitely! We’re trying to learn, after all! But books and blog posts shouldn’t be used as a hammer to bludgeon them into whatever the current One True Way of Doing Things is. And happily, I don’t think we’ve done that yet. Let’s keep it up!