The Zen of TDD

Unit testing is a practice that was admittedly all but unknown in the PHP community up until a few years ago and is not practiced nearly as widely as it should be today. It's one of things like documentation or version control that should be a widely adopted industry standard, but often gets put on the chopping block for reasons ranging from limited budgets to tight deadlines.

What is it? According to the textbook definition, unit testing is writing code (appropriately called unit tests) to test other code. Composing unit tests can take place at various points during development. The major defining trait of TDD is that tests are written before the code that they test.

There are common responses to these ideas, some of which have been discussed elsewhere. One of them goes something like, "Tests before code? But how?" To answer this question, consider black box testing. In this style of testing, tests are programmed to call a function or class method with a predefined set of input and check for predefined output.

Beyond the knowledge of the API required to make the calls involved in doing this, the tests should contain no awareness of the internals of the code they are testing. Obviously, code conducive to unit testing will reveal as little as possible about how it works. Another term for this is orthogonality or, to borrow a shorter phrase from The Pragmatic Programmer (Section 34 "Code That's Easy To Test"), "shy" code. While it's easier to test code that's in line with this idea, various methods exist to test code that isn't by implementing acceptance tests.

So the next common response is, "Well, doesn't that mean writing an entire test suite before writing code?" Not necessarily. The concept of TDD originated with XP and as such was designed to be agile or iterative / incremental in nature. Write a test before writing the code it tests, sure, but it's done gradually and methodically via a simple repeated process.

The value of tests is something that comes into question often. The number of developers and amount of time required to write test suites may seem daunting to both developers and project managers alike at first glance. There are a few things that need to be taken into perspective when considering this, however.

Point #1: While code itself obviously requires a lot of work up front, it is likely to run for a significantly longer period that it takes to produce it. The same is true of tests; they are meant to be run repeatedly and reused over time when the codebase is changed to ensure that changes don't inadvertently break other parts of the system. For time saved in tracking down issues like that, plus time saved during initial development and testing, unit tests are worth their weight in quality assurance.

Point #2: Tests also serve more purposes than just quality assurance; they can also serve as forms of documentation. One of these is project requirements, for which tests are particularly helpful when requirements are being gathered in an iterative fashion and refined over the course of the project as in the tracer bullets approach. Writing tests before code forces "real world" consideration of both the project requirements and how the API will be used once it's actually implemented. Another form of documentation tests can satisfy is technical documentation because they provide real use cases for code. When used and maintained properly, tests provide definitive examples of the code in action that outweigh any potentially outdated or incorrect intracode comments, API docblocks, or reference guide.

Point #3: For project managers, tests provide a progress metric. First, the number of passing tests in a project indicate how many of its components are functional with regard to requirements. Second, code coverage indicates how much of the project's code is actually being executed by unit tests and by proxy the level of established quality assurance for the project based on completeness of the test suite itself.

In reading The Pragmatic Programmer, one of the things that continues to amazed me is how interconnected the topics were. It seems difficult to believe that the principles gleaned from experience and documented within the book's pages have so much relation to each other merely out of coincidence. After reading this, I hope I've shown both testing and TDD are at the very least worth trying. If you'd like more information, the presentation Test Drive Your Development Process by Derick Rethans is an excellent resource to help you get started and even covers continuous integration of unit tests.