Test-Driven Development arose out of early literature around XP and other agile methods and was put forth, first and foremost, as a way to support refactoring. The idea was that writing your tests first forced you into thinking about tests, which in turn encouraged you to write more. The more tests you had, the easier it was to practice refactoring, or the art of making small incremental changes to your code to improve its structure.
Proper refactoring involves making very small organizational changes to your code, then making sure that your tests still pass. Without proper test coverage, refactoring is much more difficult.
The process of TDD is often described, in abbreviated form, as ‘‘Red, Green, Refactor,’’ meaning that first you write tests so that the tests fail (causing a ‘‘red light’’ in most testing GUIs), then you write code to make the tests pass (causing the ‘‘light’’ to turn green), and then you are free to refactor or improve the structure of your code, safe in the knowledge that the tests still pass. Once the refactoring is complete, the cycle begins again:
❑ Write tests that fail.
❑ Write code to make the tests pass.
❑ Refactor your code to improve its structure.
tests. It is always tempting to ‘‘just write a little code’’ before you start writing the tests for it. You see a problem, and you want to code your way out of it. It can be very difficult to stick to writing tests first, then writing code to make them pass. However, there are a number of compelling reasons for writing the tests first:
1. If you wait, you won’t really write the tests. This is just human nature. In almost every software development shop, the most important metric is success, which means that as soon as
your code works, you’ve accomplished your primary goal, and the organization will reward
moving on to write more code above going back to write unit tests.
2. Writing the tests first forces you, as the developer, to think about your interfaces. It is very easy to write an interface in a vacuum and then discover that it doesn’t work when someone tries to consume your interface. If you write the tests first, you are both consumer and producer, and that leads to much better design. Tests help to define your contract.
3. Tests can provide documentation that is often more accessible to other developers than
written documentation would be. Unit tests reflect the way the developer who wrote
those tests thought about how the code was designed to be used. Tests define our expectations.
If the same developer writes both the unit tests and the code to make those tests pass,
then the tests reflect the intent of that developer. Writing the tests first expresses that intent more clearly than writing tests later to fit the code you have already written
TDD can also be very useful when doing pair programming. The most effective way to use TDD to pair is for one person to write the test that expresses his or her intent and for the other person to write the code that makes that test pass. This can be done at a very physical level, passing the keyboard back and forth.
First one developer writes the test and then passes the keyboard to the second developer, who then must make the test pass. The pair then jointly decides if any refactoring needs to be done, and the cycle begins again. This time the developers switch roles, trading test writing for implementation tasks. This method is not only fun, but also leads to a very high level of productivity and very consistent interface design, because two pairs of eyes have been on the code, and two developers are forced to agree on the intent of their interface.
Test-Driven Development applies to your whole software development lifecycle, not just to the beginning of the process. Tests define the requirements of your system as understood at the time the tests were written. In fact, in some agile projects, tests might be the only concrete representation of the project’s requirements, if the developers have direct access to their customer and the customer doesn’t provide written requirements beyond story cards.
One of the most important ways to use TDD may come after the release of your project. Every time a defect in your code is reported, the first thing you should do is write a test that exposes the defect. If you get a bug report and immediately create a test that causes the bug to happen, then you can be secure in the knowledge that when your tests pass, the defect is resolved. This provides a great way to ensure success in fixing the bug, and the test remains as part of the total unit test corpus, providing regression testing so that you know the bug won’t reoccur due to future changes. Those new tests may represent aspects of the system that were never properly tested before (this is why code coverage metrics are important; more on that later), or it might mean that the developer who wrote the initial code had an incorrect or
incomplete understanding of the requirements.