Chapter22.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 PHPUnit_Framework_TestCase

Write utility methods in an abstract subclass of PHPUnit_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 PHPUnit_Extensions_TestDecorator

You can wrap test cases or test suites in a subclass of PHPUnit_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: PHPUnit_Extensions_RepeatedTest and PHPUnit_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 7.

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

Example 22.1: The RepeatedTest Decorator

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


Implement PHPUnit_Framework_Test

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

Example 22.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 22.2: A data-driven test

<?php
require_once 'PHPUnit/Framework.php';
 
class DataDrivenTest implements PHPUnit_Framework_Test
{
    private $lines;
 
    public function __construct($dataFile)
    {
        $this->lines = file($dataFile);
    }
 
    public function count()
    {
        return sizeof($this->lines);
    }
 
    public function run(PHPUnit_Framework_TestResult $result = NULL)
    {
        if ($result === NULL) {
            $result = new PHPUnit_Framework_TestResult;
        }
 
        $result->startTest($this);
 
        foreach ($this->lines as $line) {
            list($expected, $actual) = explode(';', $line);
 
            try {
                PHPUnit_Framework_Assert::assertEquals(trim($expected), trim($actual));
            }
 
            catch (PHPUnit_Framework_AssertionFailedError $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 PHPUnit_Framework_TestResult

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

Implement PHPUnit_Framework_TestListener

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

Example 22.3 shows a simple implementation of the PHPUnit_Framework_TestListener interface.

Example 22.3: A simple test listener

<?php
require_once 'PHPUnit/Framework.php';
 
class SimpleTestListener
implements PHPUnit_Framework_TestListener
{
  public function
  addError(PHPUnit_Framework_Test $test,
           Exception $e,
           $time)
  {
    printf(
      "Error while running test '%s'.\n",
      $test->getName()
    );
  }
 
  public function
  addFailure(PHPUnit_Framework_Test $test,
             PHPUnit_Framework_AssertionFailedError $e,
             $time)
  {
    printf(
      "Test '%s' failed.\n",
      $test->getName()
    );
  }
 
  public function
  addIncompleteTest(PHPUnit_Framework_Test $test,
                    Exception $e,
                    $time)
  {
    printf(
      "Test '%s' is incomplete.\n",
      $test->getName()
    );
  }
 
  public function
  addSkippedTest(PHPUnit_Framework_Test $test,
                 Exception $e,
                 $time)
  {
    printf(
      "Test '%s' has been skipped.\n",
      $test->getName()
    );
  }
 
  public function startTest(PHPUnit_Framework_Test $test)
  {
    printf(
      "Test '%s' started.\n",
      $test->getName()
    );
  }
 
  public function endTest(PHPUnit_Framework_Test $test, $time)
  {
    printf(
      "Test '%s' ended.\n",
      $test->getName()
    );
  }
 
  public function
  startTestSuite(PHPUnit_Framework_TestSuite $suite)
  {
    printf(
      "TestSuite '%s' started.\n",
      $suite->getName()
    );
  }
 
  public function
  endTestSuite(PHPUnit_Framework_TestSuite $suite)
  {
    printf(
      "TestSuite '%s' ended.\n",
      $suite->getName()
    );
  }
}
?>


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

Example 22.4: Running and observing a test suite

<?php
require_once 'PHPUnit/Framework.php';
 
require_once 'ArrayTest.php';
require_once 'SimpleTestListener.php';
 
// Create a test suite that contains the tests
// from the ArrayTest class.
$suite = new PHPUnit_Framework_TestSuite('ArrayTest');
 
// Create a test result and attach a SimpleTestListener
// object as an observer to it.
$result = new PHPUnit_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 PHPUnit_Runner_BaseTestRunner class, which the PHPUnit_TextUI_TestRunner class (the PHPUnit command-line test runner) inherits from, can be a starting point for this.