PHPUnit Cookbook



Here is a short cookbook showing you the steps you can follow in writing and organizing your own tests using PHPUnit-0.4 by Fred Yankowski - available for download here.

What you need to know when writing your own Unit Tests with PHPUnit

  1. The class name must not begin with "Test".
  2. Each test method name must begin with "test".

Simple Test Case

How do you write testing code?

The simplest way is to create print (or echo) statements in your code, that outputs HTML. By being a little bit more creative, you could create your own assert() method, and have every method call it. The limitations are obvious once you want to turn off your tests, or your project becomes sizeable.

PHPUnit tests do not require (much?) human judgement to interpret, and it is easy to run many of them at the same time. It is even easier to remove them from your code prior to delivery, as they are never really 'in' your code.

When you need to test something here is what you do:

  1. Create a new class that inherits from TestCase.
  2. Define a test_simpleadd() method.
  3. When you want to check for equality, call assertEquals(), passing in the result of the method simpleadd() and an expected result object.
  4. Create an instance of MoneyTest and run the test_simpleadd() method by passing the name of the method into the constructor.
  5. Create an instance of TestRunner.
  6. Call the TestRunner's run() method, passing in the MoneyTest object.

For example, to test that the sum of two Moneys with the same currency contains a value which is the sum of the values of the two Moneys, write:

Class MoneyTest extends TestCase {

   function test_simpleadd() {
      $money1 = new Money ( 12, "NZD" );
      $money2 = new Money ( 14, "NZD" );
      $expected = new Money ( 26, "NZD" );
      $result = $money1->add( $money2 );
      $this->assertEquals( $expected, $result, "This should pass" );
   }

};

$test = new MoneyTest( "test_simpleAdd" );
$testRunner = new TestRunner();
$testRunner->run( $test );

If you want to write a test similar to the one you have already written, write a Fixture instead. When you want to run more than one test, create Suite.

Fixture

What if you have two or more tests that operate on the same or similar sets of objects?

Tests need to run against the background of a known set of objects. This set of objects is called a test fixture. When you are writing tests you will often find that you spend more time writing code to set up the fixture than you do in actually testing values.

To some extent, you can make writing the fixture code easier by paying careful attention to the constructors you write. However, a much bigger saving comes from sharing fixture code. Often, you will be able to use the same fixture for several different tests. Each case will send slightly different messages or parameters to the fixture and will check for different results.

When you have a common fixture, here is what you do:

  1. Create a subclass of TestCase.
  2. Add instance variables for each part of the fixture.
  3. Create a constructor which accepts a parameter (the classname = String) and passes it to the superclass.
  4. Override setUp() to initialize the instance variables.
  5. Override teardown() to release any permanent resources you allocated in setUp().

For example, to write several test cases that want to work with different combinations of 12 Swiss Francs, 14 Swiss Francs, and 28 US Dollars, first create a fixture:

Class MoneyTest extends TestCase {
   var $m12CHF;
   var $m14CHF;
   var $m28USD;

   function MoneyTest( $name = "MoneyTest" ) {
      $this->TestCase( $name );
   }

   function setUp() {
      $this->m12CHF = new Money( 12, "CHF" );
      $this->m14CHF = new Money( 14, "CHF" );
      $this->m28USD = new Money( 28, "USD" );
   }

   function teardown() {
      $this->m12CHF = NULL;
      $this->m14CHF = NULL;
      $this->m28USD = NULL;
   }
};

Once you have your fixture in place, you can write as many Test Cases as you like!

Test Case

How do you write and invoke an individual test case when you have a Fixture?

Simple, as we did in Simple Test Case!, but this time use the instance variables of the class instead of creating them for each test.
  1. Write the test case method in the fixture class.
  2. Create an instance of the MoneyTest and pass the name of the test case method to the constructor.
  3. When the test is run, the name of the test is used to look up the method to run.

For example, to test the addition of a Money and a MoneyBag, write:

Class MoneyBagTest extends TestCase {
   var $m12CHF;
   var $m14CHF;
   var $m28USD;
   var $mArray1;
   var $moneybag1;
   var $moneybag2;

   function MoneyBagTest( $name = "MoneyBagTest" ) {
      $this->TestCase( $name );
   }

   function setUp() {
      $this->m12CHF = new Money( 12, "CHF" );
      $this->m14CHF = new Money( 14, "CHF" );
      $this->m28USD = new Money( 28, "USD" );

      $this->mArray1 = array();
      $this->mArray1[ ] = $this->m12CHF;
      $this->mArray1[ ] = $this->m14CHF;

      $this->moneybag1 = new MoneyBag( $this->mArray1 );
      $this->moneybag2 = new MoneyBag( $this->m12CHF, $this->m28USD );
   }

   function teardown() {
      $this->m12CHF = NULL;
      $this->m14CHF = NULL;
      $this->m28USD = NULL;
      $this->mArray1 = NULL;
      $this->moneybag1 = NULL;
      $this->moneybag2 = NULL;
   }

   function test_simpleAdd() {
      $result = $this->m12CHF->add( $this->m14CHF );
      $this->assert( new Money( 27, "CHF" ), $result, "This should fail" );
      $this->assertEquals( new Money( 27, "CHF" ), $result, "This should fail" );
   }

   function test_bagSimpleAdd() {
      $expected = $this->moneybag2->add( $this->m14CHF );
      $result = $this->m14CHF->add( $this->moneybag2 );
      $this->assertEquals( $expected, $result, "this should pass" );
   }

};

$test = new MoneyBagTest( "test_bagSimpleAdd" );
$testRunner = new TestRunner();
$testRunner->run( $test );

Once you have several tests, organize them into a Suite.

Suite

How do you run several tests at once?

As soon as you have 2 tests, you'll want to run them together. You could run the tests one at a time yourself, but you would quickly grow tired of that. Instead, PHPUnit provides an object Test Suite, which runs any number of test cases together.

For example, to run a single test case, you execute:

$test = new MoneyBagTest( "test_bagSimpleAdd" );
$testRunner = new TestRunner();
$testRunner->run( $test );

To create a suite of two test cases and run them together, execute:

$test1 = new MoneyBagTest( "test_simpleAdd" );
$test2 = new MoneyBagTest( "test_bagSimpleAdd" );
$suite = new TestSuite();
$suite->addTest( $test 1);
$suite->addTest( $test2 );
$testRunner = new TestRunner();
$testRunner->run( $suite );

Another way is to let PHPUnit extract a Suite from a TestCase. To do so you pass the name of your TestCase to the TestSuite constructor.

$suite = new TestSuite( "MoneyBagTest" );
$testRunner = new TestRunner();
$testRunner->run( $suite );

Use the manual way when you want a suite to only contain a subset of test cases. Otherwise the automatic suite extraction is the preferred way. It avoids you having to update the suite creation code when you add a new test case.

Test Suites don't have to contain TestCases. They can contain other TestSuites and TestCases together.

$suite1 = new TestSuite( "MoneyBagTest" );
$suite2 = new TestSuite();
$suite2->addTest( $suite1 );
$testRunner = new TestRunner();
$testRunner->run( $suite2 );

TestRunner

How do you run your tests and collect their results?

As I have demonstrated throughout this cookbook, a TestRunner object is created and used to display the results of the tests run.

For ease of understanding, and simplicity of code, I will not delve too far into the alternatives other than to say, when a TestCase objects run() method is called, it returns an object of type TestResult. This object can be queried to determine the success or failure of the test executed.

To do so means accessing such methods of the TestResult such as countFailures(), but I am not going to cover there use - refer to the phpunit.php file itself if you are interested in the inner workings!

-- Additions below submitted by Paul Baranowski paulbaranowski@users.sourceforge.net --

Formatting Results

To make things look pretty, put this at the top of each test page you generate:

		echo "<html>";
	 	echo "<head>";
	 	echo "<title>PHP-Unit Results</title>";
	 	echo "<STYLE TYPE=\"text/css\">";
	 	echo "include(\"phpunit/stylesheet.css\")"; 
		echo "</STYLE>";
		echo "</head>";
		echo "<body>";
 	

Or put it in the constructor of PrettyTestResult:

	
	/* Specialize TestResult to produce text/html report */
	
		Class PrettyTestResult extends TestResult {
	   	   function PrettyTestResult() {
		        $this->TestResult(); // call superclass constructor
		        echo "<html>";
		 	echo "<head>";
		 	echo "<title>PHP-Unit Results</title>";
		 	echo "<STYLE TYPE=\"text/css\">";
		 	echo "include(\"phpunit/stylesheet.css\")"; 
			echo "</STYLE>";
			echo "</head>";
			echo "<body>";
 
          		echo "<h2>Tests</h2>";
   	            	echo "<TABLE CELLSPACING=\"1\" CELLPADDING=\"1\"  
           			BORDER=\"0\" WIDTH=\"90%\" ALIGN=\"CENTER\" class=\"details\">";
           		echo "<TR><TH>Class</TH><TH>Function</TH><TH>Success?</TH></TR>";
        	    }
 		...
 		
	   	};

How to Assert Yourself

There are many ways to check for errors in PHP-Unit:

assert($booleanFailOnTrue, $optionalMessage)
The assert statement throws an error if the first argument is true.
You can also include an optional message to print when it fails.

assertEquals($expected, $actual, $message=0)
An error will be thrown if the first two values given are NOT equal.

assertRegexp($regexp, $actual, $message=false)
An error will be thrown if the regular expression does not match the second argument.

assertEqualsMultilineStrings($string0, $string1, $message="")
An error will be thrown if the two strings do not match.

You can also do your own "if" test, and if it fails call $this->fail("message").

 
 DB::connect ("mysql://root:@localhost/MyDatabaseName");    
 if (DB::isError($connection)) {
   $this->fail($connection->errorMessage());
 }

Testing Style

Put underscores between words for your function names. PHP-Unit will display the test names in lowercase even if the function name is declared with uppercase characters in them. Thus underscores are much more readable. Thus you should do this:


  function test_database_access()

Instead of:

  function testDatabaseAccess()

Because the latter will be printed as:

  testdatabaseaccess