Having a formalized unit testing infrastructure for your projects will save you time in the long run, especially when looking for bugs in code. This article introduces you to setting up a unit testing framework. It is excerpted from chapter 6 of the book Advanced PHP Programming, written by George Schlossnagle (Sams; ISBN: 0672325616).
Testing and engineering are inextricably tied forever.
All code is tested at some point—perhaps during its implementation, during a dedicated testing phase, or when it goes live. Any developer who has launched broken code live knows that it is easier to test and debug code during development than after it goes into production.
Developers give many excuses for not testing code until it is too late. These are some of the popular ones:
The project is too rushed.
My code always works the first time.
The code works on my machine.
Let's explore these excuses. First, projects are rushed because productivity lags. Productivity is directly proportional to the amount of debugging required to make code stable and working. Unfortunately, testing early and testing late are not equal cost operations. The problem is two-fold:
In a large code base that does not have a formalized testing infrastructure, it is hard to find the root cause of a bug. It's a needle-in-a-haystack problem. Finding a bug in a 10-line program is easy. Finding a bug in 10,000 lines of included code is a tremendous effort.
As the code base grows, so do the number of dependencies between components. Seemingly innocuous changes to a "core" library—whether adding additional features or simply fixing a bug—may unintentionally break other portions of the application. This is known as refactoring. As the size and complexity of software grow, it becomes increasingly difficult to make these sorts of changes without incurring time costs and introducing new bugs.
All software has bugs. Any developer who claims that his or her software is always bug-free is living in a fantasy world.
System setups are all slightly different, often in ways that are hard to anticipate. Differing versions of PHP, differing versions of libraries, and different file system layouts are just a few of the factors that can cause code that runs perfectly on one machine to inexplicably fail on another.
Although there are no silver bullets to solve these problems, a good unit-testing infrastructure comes pretty close. A unit is a small section of code, such as a function or class method. Unit testing is a formalized approach to testing in which every component of an application (that is, every unit) has a set of tests associated with it. With an automated framework for running these tests, you have a way of testing an application constantly and consistently, which allows you to quickly identify functionality-breaking bugs and to evaluate the effects of refactoring on distant parts of the application. Unit testing does not replace full application testing; rather, it is a complement that helps you create more stable code in less time.
By creating persistent tests that you carry with the library for its entire life, you can easily refactor your code and guarantee that the external functionality has not inadvertently changed. Any time you make an internal change in the library, you rerun the test suite. If the tests run error-free, the refactoring has been successful. This makes debugging vague application problems easier. If a library passes all its tests (and if its test suite is complete), it is less suspicious as a potential cause for a bug.
Note - Unit testing tends to be associated with the Extreme Programming methodology. In fact, pervasive unit testing is one of the key tenets of Extreme Programming. Unit testing existed well before Extreme Programming, however, and can certainly be used independently of it. This book isn't about singling out a particular methodology as the "one true style," so it looks at unit testing as a standalone technique for designing and building solid code. If you have never read anything about Extreme Programming, you should check it out. It is an interesting set of techniques that many professional programmers live by. More information is available in the "Further Reading" section at the end of the chapter.