Thursday, March 4, 2004

Getting Started With TDD

Someone asked me the other day about how to get started with Test Driven Development (TDD). Since I find TDD to be such an enormous benefit to my own personal software development experience, I thought I'd share how I got going in hopes that it will help someone else.

It turns out to be pretty simple from a technology standpoint. In fact, you don't really need any tools above and beyond what you already have, as TDD is a process and not a technology. That said, I've found that NUnit is a pretty handy thing to have.

You can read in all sorts of places about the general process of TDD, so I'll just summarize: you don't write any code without writing a test for it first. Yes, that means the test won't compile when you first write it. That's okay: think of it as a design document for what you want your code to do. Once you have the test calling the API the way you want it to, you can get the code written to the point where the code (and the test) compiles, and from there, to the point where the test succeeds.

Here's the thing about TDD: it's all about discipline. There's nothing stopping you from falling off the wagon and writing some code without writing a test first. None of the tools will stop you: NUnit won't care, the compiler won't care, no one except you will care. And the rest of your team if they've bought into TDD. Even running code-coverage tests during your build won't ensure that you wrote the tests before you wrote the code.

As a result, it's up to you to ensure that you follow the steps: write the test, then write the code. The thing is, after a while, it gets a lot easier because you start to see the benefits. You still get tempted, “Eh, I'm not changing much,” so it's still about discipline, but discipline is easier in the face of the tangible rewards. Here are some of the benefits I've experienced:

  • Higher code quality. Obviously, testing helps ensure what I'm writing works the way I want it to. But one of the things I didn't see coming when I started doing TDD was that by writing code that uses the code I'm working on first, I tend to get the API more right on the first try. Because a test is also a use-case.

  • Easier to remember what I was doing. When I leave a project and then come back to it later, I find TDD makes it easier to remember what I was working on, because it's almost always in one of the following well-defined states:

  • Not compiling. I was working on either writing a unit test or had finished a test for code I hadn't written yet. In either case, the compiler tells me what I need to do.

  • Compiling but test is failing. I haven't finished writing the method for the failing unit test.

  • Compiling with working unit tests. I had finished the feature I was working on last, and am ready to move on to the next.

  • Easier to make changes with confidence. This is a huge one. When I change something, I already have lots of tests that ensure that what I changed doesn't break the code.

  • Integrates into the build. If you use NUnit, it's easy to integrate into the nightly/hourly/whatever build, so when you forget to check in some configuration file, the unit tests will fail and you'll catch the problem right away.

  • Often results in better design. You've probably experienced the phenomenon that when you have two pieces of code that call your code, the demands of the second caller cause you to refactor in a way that improves your design. Well, the test is your first caller, and the working program you're actually writing is the second. So you start with two callers, and you have to think about the API design that much sooner...usually with good effect.

In short, the best way to get started is just to get started. You'll probably convince yourself that it's a good idea the first time you find a bug you wouldn't have otherwise, or come to a design realization that would have taken a lot longer in the absence of a good test suite.



  2. Two books I would highly recommend on TDD

    Refactoring by Martin Fowler
    and Test-Driven Development: By Example by Kent Beck.

    The first is about refactoring (duh!) but he has a chapter on TDD and JUnit, the second takes you through the process of bootstrapping a project with TDD and then through the process of writing a Unit test tool. The tool is written in Python for Python and the example is compelling.

  3. Thanks Kevin! Those are now on my Amazon wishlist.