Testing PHP programs with PHPUnit

The importance of testing your code cannot be overemphasized. It gives you peace of mind whenever you make any changes, including making an overhaul of the organization of your code. In this article, we describe how to use PHPUnit to test your PHP programs.

Code preparation

Before delving into the details of using PHPUnit, it is important to follow a few simple rules to make your PHP programs easy to test.

First, even though most of your PHP programs run under a Web server like apache's context, it is important that you decouple it from the Web server as much as possible. For example, reading and writing the global variables such as $_REQUEST, $_GET, $_POST, $_SERVER that are meaningful only in the Web context should be placed in a central location. All your functions should avoid manipulating these variables directly. Instead, you should copy them over to a generic data hash $data and then operate on that hash. In this way, you can just populate the $data hash and pass it to your functions whenever you need to read from or write to it.

Second, please read, understand and follow the Model-View-Controller (MVC) model if you can. The MVC model is not just useful for developing desktop GUI applications but also useful for Web applications. Separation of your business logic such as data manipulation from presentation such as whether you use a table or a graph to show the data is the key point. Following this principle your code will be easier to test.

Installation

Installing PHPUnit is very simple. You can do it through PEAR - PHP Extension and Application Repository which should have been installed if you follow the article Create a local LAMP development environment.

You should first need to run the following commands to add additional channels through which you can find the PHPUnit package:

pear channel-discover pear.phpunit.de
pear channel-discover pear.symfony-project.com

Next you can run either pear install phpunit/PHPUnit or pear install --alldeps phpunit/PHPUnit. The second command is simpler because it also installs all the dependent packages without prompting you for confirmation.

Then you can run pear list -a to verify that PHPUnit is indeed installed.

Writing test cases

Writing test cases with PHPUnit is very simple. You just need to include the PHPUnit/Framework.php file, declare a class that extends PHPUnit_Framework_TestCase and then have all your testing functions starting with the name "test" and use $this->assertTrue to check whether the condition is met. The following shows a complete but very simple example using PHPUnit.

require_once 'PHPUnit/Framework.php';

class SimpleTest extends PHPUnit_Framework_TestCase {

  public function testSimple() {
    $this->assertTrue(1+1 == 2);
    $this->assertEquals(1+2, 3);
  }
}
?>

Then you can run it with

phpunit phpunit_simple.php

and get the following output:

PHPUnit 3.4.12 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 2 assertions)

Probably that's all you need to know in most cases. If you need advanced features, you can check the PHPUnit manual.

Resolution to some common PHPUnit usage issues

Following we describe some issues that you may see when working with PHPUnit and how to address them.

Testing with PHP resources

When you test PHP functions that reuse the same resources, you may see some weird errors that say the given parameter is not a resource. Following is a simple script that reproduces the resource issue:

<?php
 
require_once 'PHPUnit/Framework.php';
global $ch;
$ch = curl_init();
 
function do_curl()
{
  global $ch;
  curl_setopt($ch, CURLOPT_URL, "http://www.yuonlamp.com/");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  $ret = curl_exec($ch);
  return strlen($ret)>0;
}
 
class CurlTest extends PHPUnit_Framework_TestCase {
  public function testCurl() {
   $this->assertTrue(do_curl());
  }
  public function testCurl2() {
   $this->assertTrue(do_curl());
  }
}
?>

When you run

phpunit phpunit_curl.php

it outputs errors like the following:

PHPUnit 3.4.12 by Sebastian Bergmann.
 
.E
 
Time: 1 second, Memory: 3.50Mb
 
There was 1 error:
 
1) CurlTest::testCurl2
curl_setopt() expects parameter 1 to be resource, integer given
 
/tmp/phpunit_curl.php:10
/tmp/phpunit_curl.php:22
 
FAILURES!
Tests: 2, Assertions: 1, Errors: 1.

The reason is that PHPUnit's backup and restoration of global variables does not work for resources. You can ask PHPUnit not do to this by adding

protected $backupGlobals = FALSE;

to your testcase class that derives from PHPUnit_Framework_TestCase.

You can download the modified PHPUnit test script that works when resources are used.

You can also check the Global State section in the PHPUnit manual for detailed discussions on this.

Back to articles on development