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.
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.
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;
}
}
?>
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>
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.
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.
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.