Prev Next

Chapter 23. 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 6.

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

Example 23.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 23.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 23.2: A data-driven test

<?php
require_once 'PHPUnit/Framework.php';
require_once 'PHPUnit/Util/Timer.php';
require_once 'PHPUnit/TextUI/TestRunner.php';
 
class DataDrivenTest implements PHPUnit_Framework_Test
{
    private $lines;
 
    public function __construct($dataFile)
    {
        $this->lines = file($dataFile);
    }
 
    public function count()
    {
        return 1;
    }
 
    public function run(PHPUnit_Framework_TestResult $result = NULL)
    {
        if ($result === NULL) {
            $result = new PHPUnit_Framework_TestResult;
        }
 
        foreach ($this->lines as $line) {
            $result->startTest($this);
            PHPUnit_Util_Timer::start();
 
            list($expected, $actual) = explode(';', $line);
 
            try {
                PHPUnit_Framework_Assert::assertEquals(trim($expected), trim($actual));
            }
 
            catch (PHPUnit_Framework_AssertionFailedError $e) {
                $result->addFailure($this, $e, PHPUnit_Util_Timer::stop());
            }
 
            catch (Exception $e) {
                $result->addError($this, $e, PHPUnit_Util_Timer::stop());
            }
 
            $result->endTest($this, PHPUnit_Util_Timer::stop());
        }
 
        return $result;
    }
}
 
$test = new DataDrivenTest('data_file.csv');
$result = PHPUnit_TextUI_TestRunner::run($test);
?>
PHPUnit 3.4.2 by Sebastian Bergmann.

.F

Time: 0 seconds

There was 1 failure:

1) DataDrivenTest
Failed asserting that two strings are equal.
expected string <bar>
difference      <  x>
got string      <baz>
/home/sb/DataDrivenTest.php:32
/home/sb/DataDrivenTest.php:53

FAILURES!
Tests: 2, Failures: 1.


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 22.13) and attach it to the PHPUnit_Framework_TestResult object, before running the tests.

Example 23.3 shows a simple implementation of the PHPUnit_Framework_TestListener interface.

Example 23.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 23.4 shows how to run and observe a test suite.

Example 23.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.

Prev Next
1. Automating Tests
2. PHPUnit's Goals
3. Installing PHPUnit
4. Writing Tests for PHPUnit
Test Dependencies
Data Providers
Testing Exceptions
Testing PHP Errors
5. The Command-Line Test Runner
6. Fixtures
More setUp() than tearDown()
Variations
Sharing Fixture
Global State
7. Organizing Tests
Composing a Test Suite Using the Filesystem
Composing a Test Suite Using XML Configuration
Using the TestSuite Class
8. TestCase Extensions
Testing Output
9. Database Testing
Data Sets
Flat XML Data Set
XML Data Set
CSV Data Set
Replacement Data Set
Operations
Database Testing Best Practices
10. Incomplete and Skipped Tests
Incomplete Tests
Skipping Tests
11. Test Doubles
Stubs
Mock Objects
Stubbing and Mocking Web Services
Mocking the Filesystem
12. Testing Practices
During Development
During Debugging
13. Test-Driven Development
BankAccount Example
14. Behaviour-Driven Development
BowlingGame Example
15. Code Coverage Analysis
Specifying Covered Methods
Ignoring Code Blocks
Including and Excluding Files
16. Other Uses for Tests
Agile Documentation
Cross-Team Tests
17. Skeleton Generator
Generating a Test Case Class Skeleton
Generating a Class Skeleton from a Test Case Class
18. PHPUnit and Selenium
Selenium RC
PHPUnit_Extensions_SeleniumTestCase
19. Logging
Test Results (XML)
Test Results (TAP)
Test Results (JSON)
Code Coverage (XML)
20. Build Automation
Apache Ant
Apache Maven
Phing
21. Continuous Integration
Atlassian Bamboo
CruiseControl
phpUnderControl
22. PHPUnit API
Overview
PHPUnit_Framework_Assert
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertType()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
PHPUnit_Framework_Test
PHPUnit_Framework_TestCase
PHPUnit_Framework_TestSuite
PHPUnit_Framework_TestResult
Package Structure
23. Extending PHPUnit
Subclass PHPUnit_Framework_TestCase
Assert Classes
Subclass PHPUnit_Extensions_TestDecorator
Implement PHPUnit_Framework_Test
Subclass PHPUnit_Framework_TestResult
Implement PHPUnit_Framework_TestListener
New Test Runner
A. Assertions
B. Annotations
@assert
@backupGlobals
@backupStaticAttributes
@covers
@dataProvider
@depends
@expectedException
@group
@outputBuffering
@runTestsInSeparateProcesses
@runInSeparateProcess
@test
@testdox
@ticket
C. The XML Configuration File
PHPUnit
Test Suites
Groups
Including and Excluding Files for Code Coverage
Logging
Test Listeners
Setting PHP INI settings, Constants and Global Variables
Configuring Browsers for Selenium RC
D. Index
E. Bibliography
F. Copyright