What is Test-Driven Development (TDD)?
Test-Driven Development is a process that relies on the repetition of very short development cycle. It is based on the test-first concept of Extreme Programming (XP) that encourages simple design with high level of confidence.
The procedure of doing TDD is following:
- Write a test
- Run all tests
- Write the implementation code
- Run all tests
This procedure is often called Red-Green-Refactor.
While writing tests we are in the red state. Since test is written before the actual implementation, it is supposed to fail. If it doesn't, test is wrong. It describes something that already exists or it was written incorrectly. Being in green while writing tests is a sign of false positive. Tests like that should be removed or refactored.
Next comes the green state. When the implementation of the last test is finished, all tests should pass. If they don't, implementation is wrong and should be corrected.
The idea is not to make the implementation final, but to provide just enough code for tests to pass. Once everything is green we can proceed to refactor the existing code. That means that we are making the code more optimum without introducing new features. While refactoring is in place, all tests should be passing all the time. If one of them fails, refactor broke an existing functionality. Refactoring should not include new tests.
Speed is the key
I tend to see TDD as a game of ping pong (or table tennis). The game is very fast. Same holds true for TDD. I tend not to spend more than a minute on either side of the table (test and implementation). Write a short test and run it (ping), write the implementation and run all tests (pong), write another test (ping), write implementation of that test (pong), refactor and confirm that all tests are passing (score), repeat. Ping, pong, ping, pong, ping, pong, score, serve again. Do not try to make the perfect code. Instead, try to keep the ball rolling until you think that the time is right to score (refactor).
It's not about testing
T in TDD is often misunderstood. TDD is the way we approach the design. It is the way to force us to think about the implementation before writing the code. It is the way to better structure the code. That does not mean that tests resulting from using TDD are useless. Far from that. They are very useful and allow us to develop with great speed without being afraid that something will be broken. That is especially true when refactoring takes place. Being able to reorganize the code while having the confidence that no functionality is broken is a huge boost to the quality of the code.
The main objective of TDD is code design with tests as a very useful side product.
In order for tests to run fast thus providing constant feedback, code needs to be organized in a way that methods, functions and classes can be easily mocked and stubbed. Speed of the execution will be severely affected it, for example, our tests need to communicate with the database. By mocking external dependencies we are able to increase that speed drastically. Whole unit tests suite execution should be measured in minutes if not seconds. More importantly than speed, designing the code in a way that it can be easily mocked and stubbed forces us to better structure that code by applying separation of concerns. With or without mocks, the code should be written in a way that we can, for example, easily replace one database for another. That "another" can be, for example, mocked or in-memory database.
An example of mocking in Scala can be found in the Scala Test-Driven Development (TDD): Unit Testing File Operations with Specs2 and Mockito article. If your programming language of choice is not Scala, article can still be very useful in order to see the pattern that can be applied to any language.
Another very useful side effect of TDD (and well structured tests in general) is documentation. In most cases, it is much easier to find out what the code does by looking at tests than the implementation itself. Additional benefit that other types of documentation cannot provide is that tests are never outdated. If there is any discrepancy between tests and the implementation code, tests fail. Failed tests mean inaccurate documentation.
Tests as documentation article provides a bit deeper reasoning behind the usage of tests instead of traditional documentation.
In my experience, TDD is probably the most important tool we have in our software toolbox. It takes a lot of time and patience to become proficient in TDD but once that art is mastered, productivity and quality increases drastically. The best way to both learn and practice TDD is in combination with pair programming. As in a game of ping pong that requires two participants, TDD can be in pairs where one coder writes tests and the other writes the implementation of those tests. Roles can switch after every test (as it's often done in coding dojos).
Give it a try and don't give up when faced with obstacles since there will be many.
Test Driven Development (TDD): Best Practices Using Java Examples is a good starting point. Even though it uses Java examples, same, if not all, practices can be applied to any programming language. For an example in Java (as in the previous case, it is easily aplicable to other languages) please take a look at TDD Example Walkthrough article.
Another great way to perfection TDD skills are code katas (there are many on this site).
What is your experience with TDD? There are as many variations as there are teams practicing it and I'd like to hear about your experience.