Chapter 15. Extending PHPUnit

PHPUnit can be extended in various ways to make the writing of tests easier and customize the feedback you get from running tests. Here are common starting points to extend PHPUnit.

Subclass PHPUnit2_Framework_TestCase

Write utility methods in an abstract subclass of PHPUnit2_Framework_TestCase and derive your test case classes from that class. This is one of the easiest ways to extend PHPUnit.

Assert Classes

Write your own class with assertions special to your purpose.

Subclass PHPUnit2_Extensions_TestDecorator

You can wrap test cases or test suites in a subclass of PHPUnit2_Extensions_TestDecorator and use the Decorator design pattern to perform some actions before and after the test runs.

PHPUnit ships with two concrete test decorators: PHPUnit2_Extensions_RepeatedTest and PHPUnit2_Extensions_TestSetup. The former is used to run a test repeatedly and only count it as a success if all iterations are successful. The latter was discussed in Chapter 5.

Example 15.1 shows a cut-down version of the PHPUnit2_Extensions_RepeatedTest test decorator that illustrates how to write your own test decorators.

Example 15.1: The RepeatedTest Decorator

<?php
require_once 'PHPUnit2/Extensions/TestDecorator.php';
 
class PHPUnit2_Extensions_RepeatedTest extends PHPUnit2_Extensions_TestDecorator {
    private $timesRepeat = 1;
 
    public function __construct(PHPUnit2_Framework_Test $test, $timesRepeat = 1) {
        parent::__construct($test);
 
        if (is_integer($timesRepeat) &&
            $timesRepeat >= 0) {
            $this->timesRepeat = $timesRepeat;
        }
    }
 
    public function countTestCases() {
        return $this->timesRepeat * $this->test->countTestCases();
    }
 
    public function run($result = NULL) {
        if ($result === NULL) {
            $result = $this->createResult();
        }
 
        for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) {
            $this->test->run($result);
        }
 
        return $result;
    }
}
?>

Implement PHPUnit2_Framework_Test

The PHPUnit2_Framework_Test interface is narrow and easy to implement. You can write an implementation of PHPUnit2_Framework_Test that is simpler than PHPUnit2_Framework_TestCase and that runs data-driven tests, for instance.

Example 15.2 shows a data-driven test-case class that compares values from a file with Comma-Separated Values (CSV). Each line of such a file looks like foo;bar, where the first value is the one we expect and the second value is the actual one.

Example 15.2: A data-driven test

<?php
require_once 'PHPUnit2/Framework/Assert.php';
require_once 'PHPUnit2/Framework/Test.php';
require_once 'PHPUnit2/Framework/TestResult.php';
 
class DataDrivenTest implements PHPUnit2_Framework_Test {
    private $lines;
 
    public function __construct($dataFile) {
        $this->lines = file($dataFile);
    }
 
    public function countTestCases() {
        return sizeof($this->lines);
    }
 
    public function run($result = NULL) {
        if ($result === NULL) {
            $result = new PHPUnit2_Framework_TestResult;
        }
 
        $result->startTest($this);
 
        foreach ($this->lines as $line) {
            list($expected, $actual) = explode(';', $line);
 
            try {
                PHPUnit2_Framework_Assert::assertEquals(trim($expected), trim($actual));
            }
 
            catch (PHPUnit2_Framework_ComparisonFailure $e) {
                $result->addFailure($this, $e);
            }
 
            catch (Exception $e) {
                $result->addError($this, $e);
            }
        }
 
        $result->endTest($this);
 
        return $result;
    }
}
 
$test   = new DataDrivenTest('data_file.csv');
$result = $test->run();
 
$failures = $result->failures();
print $failures[0]->thrownException()->toString();
?>
expected: <foo> but was: <bar>

Subclass PHPUnit2_Framework_TestResult

By passing a special-purpose PHPUnit2_Framework_TestResult object to the run() method, you can change the way tests are run and what result data gets collected.

Implement PHPUnit2_Framework_TestListener

You do not necessarily need to write a whole subclass of PHPUnit2_Framework_TestResult in order to customize it. Most of the time, it will suffice to implement a new PHPUnit2_Framework_TestListener (see Table 14.10) and attach it to the PHPUnit2_Framework_TestResult object, before running the tests.

Example 15.3 shows a simple implementation of the PHPUnit2_Framework_TestListener interface.

Example 15.3: A simple test listener

<?php
require_once 'PHPUnit2/Framework/TestListener.php';
 
class SimpleTestListener
implements PHPUnit2_Framework_TestListener {
  public function
  addError(PHPUnit2_Framework_Test $test, Exception $e) {
    printf(
      "Error while running test '%s'.\n",
      $test->getName()
    );
  }
 
  public function
  addFailure(PHPUnit2_Framework_Test $test,
             PHPUnit2_Framework_AssertionFailedError $e) {
    printf(
      "Test '%s' failed.\n",
      $test->getName()
    );
  }
 
  public function
  addIncompleteTest(PHPUnit2_Framework_Test $test,
                    Exception $e) {
    printf(
      "Test '%s' is incomplete.\n",
      $test->getName()
    );
  }
 
  public function startTest(PHPUnit2_Framework_Test $test) {
    printf(
      "Test '%s' started.\n",
      $test->getName()
    );
  }
 
  public function endTest(PHPUnit2_Framework_Test $test) {
    printf(
      "Test '%s' ended.\n",
      $test->getName()
    );
  }
 
  public function
  startTestSuite(PHPUnit2_Framework_TestSuite $suite) {
    printf(
      "TestSuite '%s' started.\n",
      $suite->getName()
    );
  }
 
  public function
  endTestSuite(PHPUnit2_Framework_TestSuite $suite) {
    printf(
      "TestSuite '%s' ended.\n",
      $suite->getName()
    );
  }
}
?>

Example 15.4 shows how to run and observe a test suite.

Example 15.4: Running and observing a test suite

<?php
require_once 'PHPUnit2/Framework/TestResult.php';
require_once 'PHPUnit2/Framework/TestSuite.php';
 
require_once 'ArrayTest.php';
require_once 'SimpleTestListener.php';
 
// Create a test suite that contains the tests
// from the ArrayTest class.
$suite = new PHPUnit2_Framework_TestSuite('ArrayTest');
 
// Create a test result and attach a SimpleTestListener
// object as an observer to it.
$result = new PHPUnit2_Framework_TestResult;
$result->addListener(new SimpleTestListener);
 
// Run the tests.
$suite->run($result);
?>
TestSuite 'ArrayTest' started.
Test 'testNewArrayIsEmpty' started.
Test 'testNewArrayIsEmpty' ended.
Test 'testArrayContainsAnElement' started.
Test 'testArrayContainsAnElement' ended.
TestSuite 'ArrayTest' ended.

New Test Runner

If you need different feedback from the test execution, write your own test runner, interactive or not. The abstract PHPUnit2_Runner_BaseTestRunner class, which the PHPUnit2_TextUI_TestRunner class (the PHPUnit command-line test runner) inherits from, can be a starting point for this.