One of my not-yet 43 things is to practice religious test-first development for a month. Well, I’m not there yet, but I’ve made a start at it.
Test-first development is one of those things that, when I first heard of it, was hard to see the value of. I mean, yah, unit testing is good-
invaluable, even-but writing your tests first? It is a mark, perhaps, of my level of technical maturity at the time, but I just couldn’t see why anyone would bother.
Since then, however, I’ve thought a lot and read a lot, and I definitely believe that writing tests first is a valuable and important skill to learn. The trick is in learning it. It really is a completely different way of thinking about software engineering.
Traditionally, when given a task I would build a quick skeleton of stubbed out functions and classes, just to start wrapping my mind around the scope of the problem. I would then start linking those stubs together with a minimal amount of code, building the application incrementally and “testing” (maybe unit testing, maybe ad-hoc) as I went, but always after writing the code. In fact, when I was first introduced to unit testing, I would write my entire application and then write the unit tests. (Seems ludicrous in retrospect, but everyone has to start somewhere.)
With test-first development, you turn the process entirely inside-out. I find that I have to spend more time thinking about the code I’m going to write, before I write it. (And that, I believe, is the whole point!) I have to make sure I understand the problem domain well enough to specify the correct behavior of the component I want to write, before I do anything about writing it. But when I do, my application comes together rapidly and correctly, almost like magic. DHH said in conversation with me (and I agree with him) that it “feels like cheating in a good way.”
Consider tonight’s exercise. Florian Groß pointed out a few bugs in my Syntax lib, and then asked if I could modify it so that it would also highlight the interpolated expressions within strings and regexps. So, instead of just hacking on the Syntax lib to make it happen, I instead sat down and wrote failing unit tests for the bugs that Florian discovered, as well as for the new feature I wanted to add (highlighted subexpressions). And you know what? It forced me to think about things I might not have seen as soon, otherwise, like “should subexpressions always be highlighted, or should it be optional?” I was able to understand what the new feature really needed to do before I made a stab and implementing it.
I’ve made it this far without testing first because I’ve learned a lot of things the hard way. I’ve got a good amount of experience behind me with writing software, and I have a set of instincts that, over the years, have come to guide me towards correct (or at least, sufficient) solutions in a reasonable amount of time and effort. But with testing first, I can already see a huge improvement. I understand things better. Solutions are less nebulous, and in large projects (like Basecamp, or Backpack, where I’m still learning how the entire system is knit together) it is about the only way I’m able to confidently fix bugs and add features.
I have to wonder what kind of a programmer I’d be today, if I’d been taught test-first development in those first, early days of my tinkering with gw-basic on my mom’s Tandy. It really is a life-changing skill. If you aren’t hooked on it yet, resolve to be! You’ll be a much better programmer for it.