What is Test Driven Development?
In simplest terms, TDD is a method of developing software that relies on writing a test first, then the code that makes the test pass. Some developers prefer to use the term Behavior Driven Development, because they feel that their tests are defining how their functional code should behave.
At a more conceptual level, TDD is often used to define the feature set that a product is supposed to contain. This is usually called Acceptance Test Driven Development.
- Write a failing test
- Write the simplest code that makes it pass
- Refactor – follow the rules of simple design
“The hardest part of TDD, however, is the final step. Some TDD novices skip it altogether, others have trouble evolving their design. Code that follows the rules of simple design
- Passes all the tests
- Contains no duplication
- Clearly expresses the programmer’s intent
- Minimizes code”
For an excellent walk-through of how TDD works in practice, see the following article: http://securesoftwaredev.com/2011/12/05/practicing-tdd-using-the-roman-numerals-kata/
TDD is an investment that pays off most when followed by everyone, all the time. During the phase of a project when the code has evolved to a high level of complexity, the high degree of test coverage ensures that changes WILL be caught by the checks in the tests. Programmers don’t have to overthink every little decision. They can make a change, verify it and move on.
TDD also pays off early on. A developer may try to foresee how the code will be used in the most generic and reusable way possible. While this is absolutely an end goal, TDD allows the developer to reach that goal organically without having to keep it all in their head or write it all down on a napkin first. Of course, this may not apply to higher level design considerations. The lower level functional design and code is created along with the tests. As the virtual napkin is written on, so the system nears completion. Over-engineering can be avoided using this approach.
- Treat test objects and test support objects as first class citizens. This means that you should not avoid writing code just for the sake of making functionality testable. Take the time to allow the injection of services and configuration whenever possible. This often pays off in other ways beyond testing.
- Separate test setup and teardown into support services where practical.
- Test the unhappy path of code execution as well as the expected happy results.
- Do not write tests that depend on the result of previous tests
- Slow running tests
- Unit Tests that cover too many methods. Save that for integration tests.
Benefits (excerpt from http://en.wikipedia.org/wiki/Test-driven_development)
“Test-driven development offers more than just simple validation of correctness, but can also drive the design of a program. By focusing on the test cases first, one must imagine how the functionality is used by clients (in the first case, the test cases). So, the programmer is concerned with the interface before the implementation. This benefit is complementary to Design by Contractas it approaches code through test cases rather than through mathematical assertions or preconceptions.
Test-driven development offers the ability to take small steps when required. It allows a programmer to focus on the task at hand as the first goal is to make the test pass. Exceptional cases and error handling are not considered initially, and tests to create these extraneous circumstances are implemented separately. Test-driven development ensures in this way that all written code is covered by at least one test. This gives the programming team, and subsequent users, a greater level of confidence in the code.
While it is true that more code is required with TDD than without TDD because of the unit test code, the total code implementation time could be shorter based on a model by Müller and Padberg.Large numbers of tests help to limit the number of defects in the code. The early and frequent nature of the testing helps to catch defects early in the development cycle, preventing them from becoming endemic and expensive problems. Eliminating defects early in the process usually avoids lengthy and tedious debugging later in the project.
TDD can lead to more modularized, flexible, and extensible code. This effect often comes about because the methodology requires that the developers think of the software in terms of small units that can be written and tested independently and integrated together later. This leads to smaller, more focused classes, looser coupling, and cleaner interfaces. The use of the mock object design pattern also contributes to the overall modularization of the code because this pattern requires that the code be written so that modules can be switched easily between mock versions for unit testing and “real” versions for deployment.“