Chapter20.PHPUnit's Implementation

The implementation of PHPUnit is a bit unusual, using techniques that are difficult to maintain in ordinary application code. Understanding how PHPUnit runs your tests can help you write them.

A single test is represented by a PHPUnit_Framework_Test object and requires a PHPUnit_Framework_TestResult object to be run. The PHPUnit_Framework_TestResult object is passed to the PHPUnit_Framework_Test object's run() method, which runs the actual test method and reports any exceptions to the PHPUnit_Framework_TestResult object. This is an idiom from the Smalltalk world called Collecting Parameter. It suggests that when you need to collect results over several methods (in our case the result of the serveral invocations of the run() method for the various tests), you should add a parameter to the method and pass an object that will collect the results for you. See the article "JUnit: A Cook's Tour" by Erich Gamma and Kent Beck [GammaBeck1999] and "Smalltalk Best Practice Patterns" by Kent Beck [Beck1997].

To further understand how PHPUnit runs your tests, consider the test-case class in Example 20.1.

Example 20.1: The EmptyTest class

<?php
require_once 'PHPUnit/Framework.php';
 
class EmptyTest extends PHPUnit_Framework_TestCase
{
    private $emptyArray = array();
 
    public function testSize()
    {
        $this->assertEquals(0, sizeof($this->emptyArray));
    }
 
    public function testIsEmpty()
    {
        $this->assertTrue(empty($this->emptyArray));
    }
}
?>


When the test is run, the first thing PHPUnit does is convert the test class into a PHPUnit_Framework_Test object -- here, a PHPUnit_Framework_TestSuite containing two instances of EmptyTest, as shown in Figure 20.1.

Figure20.1.Tests about to be run

Tests about to be run


When the PHPUnit_Framework_TestSuite is run, it runs each of the EmptyTests in turn. Each runs its own setUp() method, creating a fresh $emptyArray for each test, as shown in Figure 20.2. This way, if one test modifies the array, the other test will not be affected. Even changes to global and super-global (like $_ENV) variables do not affect other tests.

Figure20.2.Tests after running, each with its own fixture

Tests after running, each with its own fixture


In short, one test-case class results in a two-level tree of objects when the tests are run. Each test method works with its own copy of the objects created by setUp(). The result is tests that can run completely independently.

To run the test method itself, PHPUnit uses reflection to find the method name in the instance variable $name and invokes it. This is another idiom, called Pluggable Selector, that is commonly used in the Smalltalk world. Using a Pluggable Selector makes the writing of tests simpler, but there is a tradeoff: you cannot look at the code to decide whether a method is invoked, you have to look at the data values at runtime.