Test-First Development vs. Test-Driven Development
I was out having lunch with a colleague of mine and we had gotten onto the subject of TDD and how I missed doing it, even though I did test-first development where possible. He then asked me about the difference between TDD, test-driven development, and TFD, test-first development. For me, there’s a big difference, and one that I wanted to write about.
TDD Means Lots of Fast Cycles
First, some background. Up until I recently switched to a different team, I was on one of the product application teams at Guidewire Software, working on the BillingCenter insurance billing system. For architectural reasons that I won’t go into here, tests run slowly. For example, from the time I tell IDEA to start running the test and I get a success or failure result can be anywhere from 45 seconds to a couple of minutes, or even more. If you’re used to tests running quickly, which I define as completing in under 2 seconds, then you may not understand how long even 45 seconds is. Try this experiment next time you run a test: after you hit the “run” button, start a 45 second timer and force yourself to not touch the IDE or editor until the timer is done. 45 seconds is a long time to stare at the screen waiting, right? This is the difference between being able to do TDD and doing TFD.
TDD and TFD have one obvious thing in common: you write the test first, run it, watch it (hopefully!) fail, and then write production code to make it pass. Once the test passes, you may refactor, or do another cycle. You keep doing this until your story is done, or your timebox is over.
The difference is, in TDD you write very little code in each cycle, and your tests are pretty granular, which means that you may be cycling several, if not even dozens, times per minute. With TFD, you’re still writing the test before the code (so you get all of the “thinking from the caller’s point of view” benefits), but you’re spending more time each cycle writing code because the overhead of each cycle is so large. I mean, it’s huge: if you tried to take the small steps in TFD that you would do in TDD, you’d be 10-30x less productive! And that’s not even counting the breaks in context every time you’re sitting staring at the screen waiting for the test to complete. This means your tests are more complicated, the test fixtures are bigger, or you’re writing more than one test per cycle.
You can optimize the work by writing the code to make the test pass while you’re waiting for that test to run, but that’s a just a bandage over the problem. Not to mention what happens if the test ends up passing, when you thought it must fail, and you have to undo the code you wrote to understand why it passed when it shouldn’t have. This doesn’t happen often, but when it does, it’s frustrating.
TFD Means Fewer and Larger Steps
What’s interesting is that it’s exactly like working on large stories instead of small stories, i.e., breaking down the work into small pieces: batch sizes and inventory. In Lean Software development, you want to minimize work-in-progress (aka inventory, or things that are not yet Done), and Agile, in general, you want to get quick feedback on small pieces of work so that you know you’re heading in the desired direction. You also want to minimize hand-offs, where important context can be lost in the transfer.
By doing TFD, and taking larger steps—i.e., bigger batch sizes—you’re potentially writing more code than you need because for each cycle, you’re simply writing more production code (and usually more test code as well, increasing the inventory of untested tests). In TDD, you will likely end up with less code that meets the requirements of the story because for each test, you’re always writing the absolute minimum amount of code to pass that test and no more. Once the test passes, you stop writing more code and move on to the next test.
In terms of hand-offs, if you’re waiting more than 5-10 seconds for the test to finish, you’ve lost your context. This is no different then having to hand-off the context of what you were doing to someone else, except in this case, that someone else is you. If you’re pairing, you think you might not lose the context as quickly because you have your pair-partner to fall back on, but you’d be wrong. I can’t remember how many times my pair-partner and I looked at each other after a test failed, and said, “uhh, what were we doing?” Which likely means you’ll be making a mistake somewhere because you forgot something that turns out to be important.
I wish I had empirical evidence to back this up, but everyone I’ve ever talked to about this agrees that there’s a huge difference in the quality of the code when doing TDD vs. TFD (yes, this is certainly a biased sample!). No small part of it has to do with being able to stay in the Flow in TDD, that you simply can’t do in TFD.
TFD is What Happens When You’re Not Doing TDD
One lesson here is that if you’re not always doing TDD, it’s all too easy for the codebase to get to the point where it’s no longer possible to do TDD. Once you’re there, it can be extremely expensive to get back to the point of being able to do TDD, which may not even be feasible. In our situation, getting our tests to the point where we can do TDD is a huge amount of effort, one that will likely require several people 18-24 months of work. Is it worthwhile? Absolutely. Unfortunately, because it’s hard to express the quality and productivity difference between TFD and TDD, it’s not deemed an “all hands on deck” crisis, so it won’t get done soon and, in fact, may never get done (because we’re constrained by the number of good Java developers we can hire), i.e., we may simply move to a different platform first.

