PHP Unit Test documentation

The different sections of the documentation are...
  1. Quickstart guide
  2. Project overview
  3. About test cases
  4. About group tests
  5. Using server stubs to simlulate objects
  6. Using mock objects to test interactions
  7. Partial mocks for superclass testing
  8. Expectation classes
  9. Displaying results
  10. Reading web page content
  11. Testing of web forms
This page...
This documentation is shipped with the SimpleTest package.

Unit test cases

The core system is a regression testing framework built around test cases. A sample test case looks like this...


class FileTestCase extends UnitTestCase {
    function FileTestCase() {
        $this->UnitTestCase('File test');
    }
}
If no test name is supplied whe chaining the constructor then the class name will be taken instead. This will be the name displayed in the test results.

Actual tests are added as methods in the test case whose names start with the string "test" and when the test case is invoked all such methods are run in the random order that PHP introspection finds them. As many test methods can be added as needed. For example...

require_once('../classes/writer.php');

class FileTestCase extends UnitTestCase {
    function FileTestCase() {
        $this->UnitTestCase('File test');
    }
    function setUp() {
        @unlink('../temp/test.txt');
    }
    function tearDown() {
        @unlink('../temp/test.txt');
    }
    function testCreation() {
        $writer = &new FileWriter('../temp/test.txt');
        $writer->write('Hello');
        $this->assertTrue(file_exists('../temp/test.txt'), 'File created');
    }
}
Our only test method at the moment is testCreation() where we check that a file has been created by our Writer object. We could have put the unlink() code into this method as well, but by placing it in setUp() and tearDown() we can use it with other test methods that we add.

The setUp() method is run just before every test method. tearDown() is run just after every test method. You can place some test case set up into the constructor to be run once for all the cases in the test, but you risk test inteference that way. This way is slightly slower, but it is safer.

The means of reporting test results (see below) are by attached observers that are notified by various assert...() methods. Here is the full list for the UnitTestCase class, the default for SimpleTest...

assertTrue($x)Fail if $x is false
assertFalse($x)Fail if $x is true
assertNull($x)Fail if $x is set
assertNotNull($x)Fail if $x not set
assertIsA($x, $t)Fail if $x is not the class or type $t
assertEqual($x, $y)Fail if $x == $y is false
assertNotEqual($x, $y)Fail if $x == $y is true
assertIdentical($x, $y)Fail if $x === $y is false
assertNotIdentical($x, $y)Fail if $x === $y is true
assertReference($x, $y)Fail unless $x and $y are the same variable
assertCopy($x, $y)Fail if $x and $y are the same variable
assertWantedPattern($p, $x)Fail unless the regex $p matches $x
assertNoUnwantedPattern($p, $x)Fail if the regex $p matches $x
assertNoErrors()Fail if any PHP error occoured
assertError($x)Fail if no PHP error or incorrect message
assertErrorPattern($p)Fail unless the error matches the regex $p
All assertion methods can take an optional description to label the displayed result with. If omitted a default message is sent instead which is usually sufficient.

Some examples...


$variable = null;
$this->assertNull($variable, 'Should be cleared');
...will pass and normally show no message. If you have set up the tester to display passes as well then the message will be displayed as is.

$this->assertIdentical(0, false, 'Zero is not false [%s]');
This will fail as it performs a PHP === check between the two values. The "%s" part will be replaced by the default error message that would have been shown if we had not supplied our own. This allows us to nest test messages.

$a = 1;
$b = $a;
$this->assertReference($a, $b);
Will fail as the variable "$a" is a copy of "$b".

$this->assertWantedPattern('/hello/i', 'Hello world');
This will pass as using a case insensitive match the string "hello" is contained in "Hello world".

trigger_error('Disaster');
trigger_error('Catastrophe');
$this->assertError();
$this->assertError('Catastrophe');
$this->assertNoErrors();
This one takes some explanation as in fact they all pass! PHP errors in SimpleTest are trapped and placed in a queue. Here the first error check catches the "Disaster" message without checking the text and passes. This removes the error from the queue. The next error check tests not only the existence of the error, but also the text which here matches so another pass. With the queue now empty the last test will pass as well. If any unchecked errors are left at the end of a test method then an exception will be reported in the test. Note that SimpleTest cannot catch compile time PHP errors.

The test cases also have some convenience methods for debugging code or extending the suite...

setUp()Runs this before each test method
tearDown()Runs this after each test method
pass()Sends a test pass
fail()Sends a test failure
error()Sends an exception event
sendMessage()Sends a status message to those displays that support it
signal($type, $payload)Sends a user defined message to the test reporter
dump($var)Does a formatted print_r() for quick and dirty debugging
swallowErrors()Clears the error queue

Extending test cases

Of course additional test methods can be added to create specific types of test case too so as to extend framework...

if (!defined('SIMPLE_TEST')) {
    define('SIMPLE_TEST', 'path/to/simpletest/');
}
require_once(SIMPLE_TEST . 'unit_tester.php');

class FileTester extends UnitTestCase {
    function FileTester($name = false) {
        $this->UnitTestCase($name);
    }
    function assertFileExists($filename, $message = '%s') {
        $this->assertTrue(
                file_exists($filename),
                sprintf($message, 'File [$filename] existence check'));
    }
}
The SIMPLE_TEST constant is the path to the SimpleTest libraries and should be set before the first component is called. It is usually set in the top level tests.

This new case can be now be inherited just like a normal test case...

class FileTestCase extends FileTester {
    function FileTestCase() {
        $this->FileTester('File test');
    }
    function setUp() {
        @unlink('../temp/test.txt');
    }
    function tearDown() {
        @unlink('../temp/test.txt');
    }
    function testCreation() {
        $writer = &new FileWriter('../temp/test.txt');
        $writer->write('Hello');
        $this->assertFileExists('../temp/test.txt');
    }
}

If you want a test case that does not have all of the UnitTestCase assertions, only your own and assertTrue(), you need to extend the TestCase class instead. It is found in simple_test.php rather than unit_tester.php. See later if you want to incorporate other unit tester's test cases in your test suites.

Running a single test case

You won't often run single test cases except when bashing away at a module that is having difficulty and you don't want to upset the main test suite. Here is the scaffolding needed to run the a lone test case...

<?php
    if (!defined('SIMPLE_TEST')) {
        define('SIMPLE_TEST', 'path/to/simpletest/');
    }
    require_once(SIMPLE_TEST . 'unit_tester.php');
    require_once(SIMPLE_TEST . 'reporter.php');
    require_once("../classes/writer.php");

    class FileTestCase extends UnitTestCase {
        function FileTestCase() {
            $this->UnitTestCase('File test');
        }
    }
    $test = &new FileTestCase();
    $test->run(new HtmlReporter());
?>
This script will run as is, but will output zero passes and zero failures until test methods are added.

Related resources...