Test-driven development (TDD) is a programming methodology that's gained a lot of mindshare in the last few years. Although it was first described as part of Extreme Programming (XP), it's taken on a life of its own as one of the better aspects of XP. As with all buzzwords, caution is advised to all developers and suggestible IT managers. Test-driven development is a legitimate technique, but always remember there are no silver bullets.
Process
The methodology is a simple series of steps.
- Plan the mechanics of how one small bit of functionality should work.
- Write a test that verifies that bit.
- Write code to satisfy the test and no more.
- Repeat.
Benefits
TDD encourages test completeness.
Good unit tests are the backbone of any large software project. Without automated regression tests, a project of sufficient size will eventually collapse under its own weight. There is no better way to ensure good test coverage than to proclaim that all code will be written to satisfy an already-existing test.
TDD gives you a new perspective on the design process.
When you write a test first it makes you think concretely about certain aspects of how your code works. These aspects aren't necessarily more important, but they can reveal inconsistencies before they become bugs.
TDD documents the design process.
A test is an explicit declaration of a programmer's intent. Flowcharts, technical specs and even code comments all have their place in documentation, but they won't tell you when their assumptions no longer hold true.
TDD formalizes what you would be doing by hand.
Writing tests incurs some overhead, but realistically any programmer has to test all their code before committing it anyway. The overhead is quickly amortized over the life of the project.
Drawbacks
TDD ignores top-down development
TDD keeps the developer focused on the details, making it easy to lose the forest for the trees. If some of the high-level details change, it could render all of the low-level work useless.
TDD is only as good as the test harness
Although any testing framework will make it possible to test logical units of your code, often the ideal test involves complex semantics that aren't readily available. To get around this you may write sloppy tests, or use an inferior architecture just to make testing work better.
Testing some things is a waste of time
Unit tests generally follow the law of diminishing returns. The first one verifies that the environment is set up correctly and everything compiles. A few more can offer basic coverage of all your code. Beyond that you start to devolve into minutiae. So at some point it's more time effective to debug known problems than to preemptively write tests for every possible issue.
One way isn't right for all situations
Sometimes writing a test first results in premature design decisions that have to be scrapped down the line. There's no magic formula here, all you can do is keep an open mind and constantly re-evaluate your methodologies.
Writing Good Tests
Writing unit tests is an art unto itself. The entire process is subjective. Whereas code must do what it is meant to do, tests are selective observers. What they observe is what the programmer deems important; they are a window into his psyche.
Writing good tests requires experience. First ask yourself:
- What is most likely to go wrong?
- What is most likely to change?
- What small errors would precipitate a business crisis?
- Which design decisions are fundamental to the project?
- Which are incidental?
Munge all that in your head, and mix in some boilerplate guidelines:
- Each test should test only one thing
- Tests should be simple and obvious—no chance for a bug in the test
- Every line of program code should have at least one test that runs it
The key to effective test-driven development is fluency in your chosen testing environment. You need to practice writing tests and making the aforementioned judgments. Over time you will see that some tests end up being more trouble than they are worth, requiring frequent fixes to keep them valid. Other tests end up alerting you to unanticipated problems that you never would have expected. Once you are fluent, the testing overhead diminishes and you start to see great returns. Test-driven development forces the developer to sharpen his testing skills. This is far more beneficial than strict adherence to a rigid methodology per se.