ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Testing PHP Code with PHPUnit

by Sebastian Bergmann, author of PHPUnit Pocket Guide
12/08/2005

The release of PHP 5 in July 2004 marked a significant leap in PHP's evolution--it enabled the development of large-scale applications to leverage the productivity, maintainability, and reusability of object-oriented design. While the language features introduced in PHP 5 are vital for this, the development of large-scale applications requires additional tools and techniques to be viable.

PHPUnit is such a tool. It supports the development of object-oriented PHP applications using the concepts and methods of Agile Programming, Extreme Programming, Test-Driven Development, and Design-by-Contract Development by providing an elegant and robust framework for the creation, execution, and analysis of unit tests.

Automating Tests

Even good programmers make mistakes. The difference between a good programmer and a bad programmer is that the good programmer uses tests to detect his mistakes as soon as possible. The sooner you test for a mistake, the greater your chance of finding it and the less it will cost to find and fix. This explains why leaving testing until just before releasing the software is so problematic. Most errors are not caught at all, and the cost of fixing the ones you do catch is so high that you have to perform triage with the errors because you just cannot afford to fix them all.

Testing with PHPUnit is an activity not totally different from what you should already be doing. It is just a different way of doing it. The difference is between testing (that is, checking that your program behaves as expected) and performing a battery of tests (using runnable code fragments that automatically test the correctness of parts, or units, of the software). These runnable code fragments are called unit tests.

Imagine that your current task is to test PHP's built-in Array. One feature to test is the function sizeof(). For a newly created array, you expect the sizeof() function to return 0. After an element is added, sizeof() should return 1. When you start to test the numerous array_*() functions PHP offers, you need to write a test for each of them. You could write the infrastructure for all these tests from scratch. However, it is much better to write a testing infrastructure once and then write only the unique parts of each test.

Testing with PHPUnit

PHPUnit is such an infrastructure. It is a family of PEAR packages (PHPUnit, PHPUnit2) that I designed after proven solutions from the Java world (JUnit, junitour, and TestDox) and that is available from the PHP Extension and Application Repository (PEAR), a framework and distribution system for reusable PHP components. You can install it using the PEAR Installer by running

pear install PHPUnit2

Due to PEAR's version naming standard, the PHPUnit package for PHP 5 is PHPUnit2.

The following example shows how you have to write your two tests to use them with PHPUnit.

Example 1. Testing Array and sizeof() with PHPUnit

<?php
require_once 'PHPUnit2/Framework/TestCase.php';

class ArrayTest extends PHPUnit2_Framework_TestCase {
    public function testNewArrayIsEmpty() {
        // Create the Array fixture.
        $fixture = Array();

        // Assert that the size of the Array fixture is 0.
        $this->assertEquals(0, sizeof($fixture));
    }

    public function testArrayContainsAnElement() {
        // Create the Array fixture.
        $fixture = Array();

        // Add an element to the Array fixture.
        $fixture[] = 'Element';

        // Assert that the size of the Array fixture is 1.
        $this->assertEquals(1, sizeof($fixture));
    }
}
?>

Example 1 shows the basic steps for writing tests with PHPUnit:

Invoke the PHPUnit command-line test runner through the phpunit command. The following code shows how to run tests with the PHPUnit command-line test runner:

$ phpunit ArrayTest
PHPUnit 2.3.0 by Sebastian Bergmann.

..

Time: 0.067288

OK (2 tests)

For each test run, the PHPUnit command-line tool prints one character to indicate progress:

.
Printed when the test succeeds.
F
Printed when an assertion fails while running the test method.
E
Printed when an error occurs while running the test method.
I
Printed when the test is marked as being incomplete or not yet implemented.

PHPUnit distinguishes between failures and errors. A failure is a violated PHPUnit assertion. An error is an unexpected exception or a PHP error. Sometimes this distinction proves useful, since errors tend to be easier to fix than failures. If you have a big list of problems, it is best to tackle the errors first and see if you have any failures left when you have fixed them all.

Change the expected value in the testArrayContainsAnElement() test from 1 to 0 and the test will fail. The following example shows how PHPUnit reports this failure:

$ phpunit ArrayTest
PHPUnit 2.3.0 by Sebastian Bergmann.

.F

Time: 0.001147
There was 1 failure:
1) testArrayContainsAnElement(ArrayTest)
expected same: <0> was not: <1>
/home/sb/ArrayTest.php:21

FAILURES!!!
Tests run: 2, Failures: 1, Errors: 0, Incomplete Tests: 0.

The report shows where the failure occurs in the test code: in the test method testArrayContainsAnElement() of the test case class ArrayTest, in line 21 of the /home/sb/ArrayTest.php test code source file.

Sharing Setup Code

One of the most time-consuming parts of writing tests is writing the code to set the world up in a known state and then return it to its original state when the test is complete. This known state is the fixture of the test.

In Example 1, the fixture was simply the array stored in the $fixture variable. Most of the time, though, the fixture will be more complex than a simple array, and the amount of code needed to set it up will grow accordingly. The actual content of the test gets lost in the noise of setting up the fixture. This problem gets even worse when you write several tests with similar fixtures. Without some help from the testing framework, you would have to duplicate the code that sets up the fixture for each test you write.

PHPUnit supports sharing the setup code. Before running a test method, PHPUnit invokes a template method called setUp(). With setUp() you create the objects against which you will test. Once the test method has finished running, whether it succeeded or failed, PHPUnit invokes another template method called tearDown(). In tearDown() you clean up the objects against which you tested.

You can now refactor Example 1 and use setUp() to eliminate the code duplication. First declare the instance variable, $fixture, to use instead of a method-local variable. Then put the creation of the Array fixture into the setUp() method. Finally, remove the redundant code from the test methods and use the newly introduced instance variable, $this->fixture, instead of the method-local variable $fixture with the assertEquals() assertion method.

Example 2. Using setUp() to create the Array fixture

<?php
require_once 'PHPUnit2/Framework/TestCase.php';

class ArrayTest extends PHPUnit2_Framework_TestCase {
    protected $fixture;

    protected function setUp() {
        // Create the Array fixture.
        $this->fixture = Array();
    }

    public function testNewArrayIsEmpty() {
        // Assert that the size of the Array fixture is 0.
        $this->assertEquals(0, sizeof($this->fixture));
    }

    public function testArrayContainsAnElement() {
        // Add an element to the Array fixture.
        $this->fixture[] = 'Element';

        // Assert that the size of the Array fixture is 1.
        $this->assertEquals(1, sizeof($this->fixture));
    }
}
?>

setUp() and tearDown() will run once for each test method run. While it might seem frugal to run these methods only once for all the test methods in a test-case class, doing so would make it hard to write tests that are completely independent of each other.

What PHPUnit Has to Offer

Automated tests give you justified confidence in your code. You can use this confidence to take more daring leaps in improving your design (Refactoring), get along with your teammates better (Cross-Team Tests), improve relations with your customers, and go home every night with proof that because of your efforts the system is better now than it was this morning. Once you get used to writing automated tests, you will likely discover more uses for tests.

Extreme Programming, for example, demands collective code ownership, so all developers need to know how the entire system works. If you are disciplined enough to consequently use "speaking names" for your tests that describe what a class should do, you can use PHPUnit's TestDox feature to generate automated documentation for your project based on its tests. This documentation gives developers an overview of what each class of the project is supposed to do.

Code-Coverage Analysis gives you an insight into what parts of the production code execute when the tests are run and helps to measure testing completeness.

PHPUnit for PHP 5 is actively developed and maintained by Sebastian Bergmann. He is currently working on support for Mock Objects in PHPUnit 2.4, which he hopes to release later this year.

Sebastian Bergmann is the author of a variety of PHP software projects such as PHPUnit and phpOpenTracker.

PHPUnit Pocket Guide

Related Reading

PHPUnit Pocket Guide
By Sebastian Bergmann

Return to the PHP DevCenter.

Copyright © 2009 O'Reilly Media, Inc.