TDD – Test Driven Development
Let me tell you a little about my experiences with TDD, how it’s added to the enjoyment I get from writing software, and how the quality of my source code has improved as a result.
To explain the basics of TDD to those who have never used it before, we should perhaps think about the traditional form of testing. Traditionally, the three stages of software development are Planning, Implementation, and Testing. A developer would spend roughly equal amounts of time on each of these steps. The problem here is that although the plans seem bulletproof and the code may not be too difficult to write, the testing just happens far too late in the game. Bug reports get back to the developer after the software is already built, but obviously, with nearing deadlines, this is far from an ideal situation.
In reality, the process has never been quite as clear-cut as those three steps, however. Developers usually test small portions of their code once they’ve finished writing them; we call those small portions of code “units”. Whether you’re using the system from an end-user perspective, or you’re adding debug output to the code and emulating particular scenarios the software might encounter, this sort of testing gets tedious very quickly. There are only so many times somebody can perform the manual checking of values before they tire of repeating the process and simply stop. Traditionally, it’s unlikely that a developer would return to aspects of the system they’ve already written and perform that manual testing once again. As a result, small changes here and there are likely to introduce hidden bugs in other parts the system.
Test-driven development, on the other hand, goes against the grain a little. The process looks more like Planning, Designing, Testing, Implementation, Refactoring, Designing, Testing, Implementation, Refactoring … and so on, with far less emphasis on the planning phase as compared with traditional development. The Designing step is important. In order to write a test, the developers need to know what they’re testing. To do this, they’re constantly designing very isolated components in the system. This encourages a great deal of flexibility with the code, since readily testable code and flexibility often go hand in hand.
TDD usually starts with a unit test framework. That is not to say that test-driven development is unit testing─it’s not. Because I develop primarily with PHP, I chose , written by . PHP offers another popular unit test framework called , but I opted for SimpleTest because it was the more mature of the two, and had more accessible support (largely due to the fact the author is a regular visitor here on the ). Almost all unit test frameworks are extremely similar. ─Java’s most widely used unit test framework─brought this methodology into the mainstream. Various frameworks written in other programming languages subsequently emerged more-or-less following JUnit’s minimal API. We call this group of frameworks the xUnit frameworks.
How do we test the system before we’ve implemented it, though? The philosophy seems flawed at first glance, but once the concept begins to sink in, it makes perfect sense. The tests themselves are written in code and make various assertions about the behaviour of the system under test (SUT). Writing the test is done inside a unit test framework such as SimpleTest. The idea is to write a small, simple test that expresses a behavioural requirement. For example, the SUT may be expected to return a negative value when passed a particular set of arguments. The unit test framework provides the means to make these assertions, and gives useful feedback to the developer if one or more of those assertions fail.
In TDD, the developer will write a deliberately failing test (since the SUT won’t have any implementation for the requirement yet), then go on to write the most obvious and simplest code to make this test pass. Once the test passes, the developer writes the next (probably failing) test, implements a little code to make it pass, and then moves on. The end result is that over time you have two large sets of source code─one is the test code itself; the other is the source code of the software. It’s highly probable that there will be more lines of code providing test coverage than there will be actual source code. There are several benefits of keeping all that test code in your project directory:
- Running the tests ensures that the behavioural requirements they specify are still met.
- They provide support to developers who wish to modify the software without breaking or changing its existing behaviour.
- They provide a form of documentation for developers who need to understand how the software works.
All the xUnit frameworks provide mechanisms to group these tests together into a test suite and to run them all in one go, automatically. This makes the process of retesting the software far less cumbersome and much more appealing than all that traditional manual testing. More
- TDD is difficult to use in situations where full functional tests are required to determine success or failure. It is much harder to do test-first functional testing, as the system needs to be brought up to run a test. Examples of these are GUIs (graphical user interfaces), programs that work with relational databases, and those that work (or fail to work) in different network configurations. Test frameworks for many such systems are available, however the focus has to be on writing tests during/after development, rather than before.
- Management support is essential. Without the entire organization believing that Test-Driven Development is going to improve the product, management will feel that time spent writing tests is wasted .
- Testing has historically been viewed as a lower status position than developer or architect. This can be seen in products such as Visual Studio 2005, whose Architect Edition  lacked the testing facilities that the Testing Edition offered .
- The tests themselves can become part of the maintenance overhead of a project. Badly written tests, that search for hard-coded (rather than shared) error strings, or which are themselves prone to failure, are expensive to maintain. There is a risk that a test that generates false positives will be ignored, so that when it does finally detect a real failure, this will not be detected. It is critical to write tests for low/easy maintenance.
The following is a representative list of TDD tools available to you.
- csUnit (.Net)
- DUnit (Delphi)
- PyUnit (Python)
- Test::Unit (Ruby)