Friday, July 14, 2006

Test Driven Design: Not Just For Weenies?

During the last couple of weeks, I've given Test Driven Design a serious try. I have been pleasantly surprised. Beyond the expected benefit of having better test coverage, I have found that it leads (at least in the case of the last two weeks) to more elegant design that does just what it is supposed to do and no more.

I first heard about TDD years ago, but wrote it off as eXtreme programming weeny stuff. Extreme programming is a process for writing software that mandates a set of practices, including:
- all code is written in pairs
- tests are written before the code; no functionality is allowed unless a test verifies it (this is test driven development)

I had a pretty strong reaction, my thinking went along these lines: why should I be forced to write code in a very particular way sitting next to someone? What if I didn't like that person? What if he didn't like me? (I would have used "he/she", but come on, this is programming.) And if studies show that it increases productivity, it must be because, in the average case, you're taking a bunch of miserable people who work on boring code and forcing them to sit together where they won't feel comfortable surfing the web and reading blogs all day to avoid writing boring code. It's like having the boss stop by every 2 minutes forcing you to alt-tab away from reddit and slashdot. Plus, testing is the boring part of programming, you mean I have to do it all the time? Can't someone from another team write tests afterwards? I'm a good programmer, I don't need some process to enforce quality in my code. Finally, the fact that it was named, "eXtreme programming" made me want to barf; you think a whiff of super macho man / slim jim / mountain dew bull shit is going to get me excited about you're crappy software practice you invented just so you could write a bunch of books about it? Huh? Did you!?

So what made me give the test driven development part a try? Well, I started working at a real company on a real project. Your code lives forever. Any problems will come back to haunt you. And once your code is live on a production website or service, fixing it isn't a matter of changing a few lines in a file, and then checking to see if the problem is fixed. You have to patch a change list into a special branch that has to go through an entire quality assurance process. Then it goes on a canary (a subset of production machines) where everyone waits to see if things seem to be going OK. Finally, it is pushed out to all production machines. At this point, at least a day or two has passed and many people have had to work to make it happen. Then, the problem might still be there! Oh crap! Anyway, you learn to fear seeing an email pop up in your inbox about some stack trace in the production logs.

Contrast this with back in school, where each project was finite. I wrote my code, got it to "work" and then it was done. If there was a bug or two, eventually exposed by the automatic grading script, well, hey, I still got an A-. Afterwards, I could throw it away, never forced to face any bugs.

Anyways, after a couple of those emails I mentioned earlier popping up in my inbox at work followed by a scramble to hack together a change as fast as possible to fix the problem, I got a lot more serious about verifying my code. I watched a tech talk online about test driven development. I picked up a book from a local used book store named, "Unit Testing in Java: How Tests Drive the Code." As I walked back to my apartment from the bookstore, I found myself turning the cover of the book towards my body, half expecting someone to drive by in a convertible and yell, "Weeny!"

Before reading about it, I had assumed writing tests as I programmed would help in a a simple yet direct way: I would be writing tests about each piece of code before I forgot about all of the functionality I was writing. No more scrambling to write tests after the fact. That is a benefit, but what I have learned from looking into it more deeply and giving it a try is that the process reaches beyond producing bug free code; it's producing modular, verifiable code. Most importantly, it avoids over design.

No code is written that isn't verified by a test. In fact, you write the test first, run it, see that it fails, and then write the code to make the test pass. That test is a specification for behavior. In essence, you're forced to think in a very concrete way, what is this code supposed to do? What exactly are it's input, outputs, error conditions, etc? And when you have finished writing the tests, you look at your code, it is amazingly simple. That polymorphic hierarchy I was anticipating writing? Well, it turns out a couple of if-else statements were perfectly sufficient. That design pattern I was chomping at the bit to apply? Totally needless in this situation. Some of that stuff does appear, but only after there is a proven need for it, namely code duplication.

There are a bunch of other benefits of TDD that I've read about, like the fact that when someone else comes along a couple years from now and has to modify my code to do something new, they will feel a lot more confident in making sure they haven't screwed anything up. We'll see, it hasn't been long enough to verify the rest.

In the meantime, I'll keep applying it the code I write and see if I continue to like it.

You might be wondering, have I drank the XP kool-aid? Will I start bugging my teammates to buddy up and sit next to me while I program, and start watching them? For now, no. But I will give those XP guys a little credit. Weenies.