PHPUnit is an open source unit testing software framework for software written in the PHP programming language. Created by Sebastian Bergmann, PHPUnit is one of the xUnit family of frameworks that originated with Kent Beck’s SUnit. This article will explore PHPUnit specifically as a way to introduce the basics of automated unit testing. You’ll need a basic grasp of the PHP programming language to proceed!

Author: Kendrick Curtis, Stainless Software, http://www.stainless-software.com/

Introduction

Unit testing is the process of testing discrete code objects – such as functions, classes or methods – by using either bespoke testing code or by utilizing a pre-existing testing framework such as JUnit, PHPUnit or Cantata++. Unit testing frameworks provide a host of common, useful functionality for writing automated unit tests such as assertions to check whether a certain value was as expected and often include reports of line and decision coverage to tell you how much of your codebase you have.

Installation

PHPUnit is available as a PEAR package, a Composer bundle or as a PHAR file. However you install it, you must also install the PHP Code Coverage dependency first. In PEAR, you need to add the phpunit.de channel and then install the two packages from the command line:

(Note that at the time of writing, the default PEAR installation with XAMPP is broken: you’ll need to get the PEAR PHAR and install that from the command line before attempting the above).

Testing A Simple Class

Consider an extremely simple PHP class with a single method:

class TruthTeller

{

public function() tellTruth

{

return true;

}

}

Sure, tellTruth always returns true now, but how can we check that with a unit test to ensure that it always returns this result in the future?

With PHPUnit, every set of tests is a class extended from the PHPUnit_Framework_TestCase class, which provides common useful functionality such as assertions. Here’s a basic test for the tellTruth method above:

require_once 'PHPUnit/Autoload.php';

require_once 'TruthTeller.class.php';

class TruthTester extends PHPUnit_Framework_TestCase

{

function testTruthTeller()

{

$tt = new TruthTeller();

$this->assertTrue($tt->tellTruth());

}

}

Note that you need to include both the PHPUnit autoloader and also the “object under test”, in this case, the TruthTeller class file.

All we are doing with the rest of the code is asserting that if the tellTruth method is called, it will return true. These assertions are the core of PHPUnit – they are what will determine whether a test passes or fails.

If you were to fire up a command prompt, switch to the directory with your tests in and run phpunit TruthTester (the parameter is the file name of your tests file, minus the .php extension), PHPUnit will run all of the tests it can find in the file specified (a test being any method that begins with the word “test”).

And if you go back to the TruthTeller class and change it to return FALSE, you’d see something more like this:



This is the core of unit testing – writing assertions that pass or fail. When assertions that previously passed start to fail in later test runs, you know that a code change has been made that’s negatively affecting your existing code.

More Complex Tests

In reality, of course, you’ll be dealing with considerably more complex situations than the one above. A common test would be to check whether a method returned an array of data of a particular structure.

class ArrayTeller

{

public function outputArray()

{

return array(1,2,3);

}

}

A simple test for this method might look like this:

class ArrayTester extends PHPUnit_Framework_TestCase

{

function testArrayTeller()

{

$at = new ArrayTeller();

$result = $at->outputArray(1);

$this->assertInternalType("array", $result);

$this->assertCount(3, $result);

$this->assertEquals(1, $result[0]);

$this->assertEquals(3, $result[2]);

}

}

As you can see, there are a variety of things that can be checked with a unit test with a single line of code in PHPUnit: we can check that ArrayTeller returns an array rather than any other data type, we can check the length of the array and we can check the individual values in the array. Depending on your needs, there should be an assertion to match, and if you need something more complex – for example, you want to ensure that a return value falls in a range of two integers – so long as you can express it as the outcome of an if statement you can test with assertTrue. Check out the PHPUnit documentation for a list of all the available assertions.

Testing Code Paths

Unit testing is partly about writing tests that cover the expected behaviour of the method under test, preferably by reference to a specification document, but if you are writing tests to cover existing code then it’s useful to consider unit tests as a form of white-box testing. If you know that a method is a simple switch like this:

class Switcher

{

public function aOrB($switch, $a, $b)

{

if ($switch == TRUE)

{

return $a;

}

else

{

return $b;

}

}

}

… then you know that you’re going to want to write two tests, one for each case. But you ought to question how you know this – if this method changes in future to return $a for true, $b for false and throw an exception otherwise, that should ideally be in a specification document somewhere. Anyway, the tests for the above would look like this:

class SwitcherTester extends PHPUnit_Framework_TestCase

{

function testSwitchTrue()

{

$switcher = new Switcher();

$result = $switcher->aOrB(TRUE, 1, 2);

$this->assertEquals(1, $result);

}

function testSwitchFalse()

{

$switcher = new Switcher();

$result = $switcher->aOrB(FALSE, 1, 2);

$this->assertEquals(2, $result);

}

}

Running both tests is as simple as running phpunit SwitcherTester from the command line.

Using setUp to Simplify Multiple Tests

As your tests have to cover more and more combinations of inputs and set up data, it is useful to start relying on a helpful function: setUp. setUp is a method of the PHPUnit_Framework_TestCase class that you can override to provide code which is run before each and every test in the class. (Note: there is a similar method, tearDown, which is run immediately after every test – this is really only useful for things like closing sockets and file pointers).

A simple example of how this can streamline your code follows. Consider a method that relies on some object data and an input:

class DataTeller

{

private $data;

public function __construct($data)

{

$this->data = $data;

}

public function outputData($switch)

{

if ($switch == TRUE)

{

if (!empty($this->data))

return $this->data;

else

return FALSE;

}

else

{

return "switch off";

}

}

}

If we continued with our naïve scheme above, we’d write three tests and instantiate three DataTeller objects, on in each test. However, with setUp, we can outsource the construction of the DataTellers, at least for 2 cases out of 3. It is only the final test that requires a new DataTeller to be constructed.

class DataTellerTester extends PHPUnit_Framework_TestCase

{

private $dt;

protected $data = "valid data";

function setUp()

{

$this->dt = new DataTeller($this->data);

}

function testOutputArraySwitchOff()

{

$this->assertEquals("switch off", $this->dt->outputData(FALSE));

}

function testOutputArraySwitchOn()

{

$this->assertEquals($this->data, $this->dt->outputData(TRUE));

}

function testOutputArrayEmptySwitchOn()

{

$new_dt = new DataTeller("");

$this->assertEquals(FALSE, $new_dt->outputData(TRUE));

}

}

Conclusion

PHPUnit uses assertions to tell you if your code is working as expected. You should now be able to write some simple tests to cover classes with relatively self-contained functionality. However, the real challenge comes when using classes that interoperate with each other. Tune in next time to learn how to write tests for static classes, and how to use mock objects and stubs in order to isolate your object under test from the other code in its environment.

Further Reading

* PHPUnit Manual

* PHPUnit tutorial on the PEAR site

About the Author

Kendrick Curtis is a web developer with ten years experience. He is co-founder of Stainless Software, a freelance web design, development, testing and content authoring company. More info on http://www.stainless-software.com/