PHPUnit gives you a simple
framework for creating a test application to
automate testing of functions and classes.
PHPUnit is inspired by
JUnit.
Kent Beck and Erich Gamma created JUnit as a tool
for
eXtreme Programming. One of the parts of XP
is to test small software components as often and early
as possible and not to fix bugs and errors in the API
while setting up and testing the whole application.
You have not to switch to XP to benefit from PHPUnit. It
is a good tool for testing classes or a set of functions
and helps you to avoid endless debug sessions.
Work routine
Normally, you would write a class, do some unsystematic tests
using echo() or var_dump().
After this, you use the class in your application and hope everything
is ok.
To benefit from PHPUnit you should rethink the flow. The best way is
to do this:
1. design your class/API
2. create a test suite
3. implement the class/API
4. run the test suite
5. fix failures or errors and go to #4 again
This seems to require a lot of time, but this impression is
wrong. Creating the test suite using PHPUnit needs only a few minutes
and running the test suite only seconds.
Design a class
Let's start with a small example: a string class. First we create
a bunch of functions declarations to work on a string:
---- string.php ----
<?php
class String
{
//contains the internal data
var $data;
// constructor
function String($data) {
$this->data = $data;
}
// creates a deep copy of the string object
function copy() {
}
// adds another string object to this class
function add($string) {
}
// returns the formated string
function toString($format) {
}
}
?>
Creating test suite
Now we can create a test suite, which checks every
function of your string class. A test suite is normal
PHP class inherited from PHPUnit_TestCase
containing test functions,
identified by a leading 'test' in the function name.
In the test function an expected value has to be compared
with the result of the function to test.
The result of this compare must delegate to a function
of the assert*()-family, which decides
if a function pass or fail the test.
---- testcase.php ----
<?php
require_once 'string.php';
require_once 'PHPUnit.php';
class StringTest extends PHPUnit_TestCase
{
// contains the object handle of the string class
var $abc;
// constructor of the test suite
function StringTest($name) {
$this->PHPUnit_TestCase($name);
}
// called before the test functions will be executed
// this function is defined in PHPUnit_TestCase and overwritten
// here
function setUp() {
// create a new instance of String with the
// string 'abc'
$this->abc = new String("abc");
}
// called after the test functions are executed
// this function is defined in PHPUnit_TestCase and overwritten
// here
function tearDown() {
// delete your instance
unset($this->abc);
}
// test the toString function
function testToString() {
$result = $this->abc->toString('contains %s');
$expected = 'contains abc';
$this->assertTrue($result == $expected);
}
// test the copy function
function testCopy() {
$abc2 = $this->abc->copy();
$this->assertEquals($abc2, $this->abc);
}
// test the add function
function testAdd() {
$abc2 = new String('123');
$this->abc->add($abc2);
$result = $this->abc->toString("%s");
$expected = "abc123";
$this->assertTrue($result == $expected);
}
}
?>
The first test run
Now, we can run a first test. Execute this PHP program.
Make sure that the paths are correct.
If you call this script through commandline, you will get
this output:
TestCase stringtest->testtostring() failed: expected true, actual false
TestCase stringtest->testcopy() failed: expected , actual Object
TestCase stringtest->testadd() failed: expected true, actual false
Every function fails the test, because your string functions
didn't returned what we defined as the expected value.
If you want to call the script through your browser, you have to put
the script in a correct html page and call $result->toHTML
() instead of $result->toString().
Implementation
Ok, let's start with implementation of the our string class.
---- string.php ----
<?php
class String
{
//contains the internal data
var $data;
// constructor
function String($data) {
$this->data = $data;
}
// creates a deep copy of the string object
function copy() {
$ret = new String($this->data);
return $ret;
}
// adds another string object to this class
function add($string) {
$this->data = $this->data.$string->toString("%ss");
}
// returns the formated string
function toString($format) {
$ret = sprintf($format, $this->data);
return $ret;
}
}
?>
Hm, such a big expenditure for testing three simple
functions? Don't forget, this is a small example.
Think about bigger, complexer API's like database
abstraction or basket classes in a shop application.
PHPUnit is an excellent tool to detect errors in the
implementation. Maybe you have a big class used
in a few applications and you want to reimplement
the class. With a test suite, you can easily check
and fix the new implementation in a short session.