HTML5 Zone is brought to you in partnership with:

Paulund is a website dedicated to writing tutorials and code snippets about Web Development, the main subjects are PHP, Wordpress, jQuery, CSS3 and HTML5. Paul is a DZone MVB and is not an employee of DZone and has posted 139 posts at DZone. You can read more from them at their website. View Full User Profile

Test Driven Development With PHP

02.21.2013
| 3907 views |
  • submit to reddit

In a previous post I wrote about the benefits of using Test Driven Development in your application development process. This can be used in all types of development from software to web to front-end to back-end languages. In this post I will go through the steps of using Test Driven Development in PHP.

PHPUnit

PHPUnit is one of the most popular automated testing frameworks for PHP it allows you to create your own unit tests for your test driven development process.

In order to use PHPUnit you need to have it installed on your Apache server by using the PEAR installer.

You can see how to install this on the PHPUnit Github Page.

PHPUnit On Github

For a step by step guide on using PHPUnit in automated testing they have created indepth documentation here.

PHPUnit Documentation

Simple Test

Simple Test is an easier way of using PHPUnit it is a framework for PHPUnit and the web test framework. The difference is that PHPunit is done purely on the command line when Simple Test allows you to navigate to a web page and you will be displayed with the results of the tests. It makes it very easy to use in your development process.

Simple test pass

Simple test fail

Download Simple Test

Installing PHPUnit With WAMP

There are many different ways of having your development environment and I'm not here to say what is the best way is or why I use my environment, you use what ever suits you best. On my local development machine I'm using Windows with WAMP as my Apache server.

If you are using the same this is how you will install PHPUnit with WAMP.

First you need to install PEAR as this is needed to use PHPUnit, these are the steps to install PEAR and PHPUnit.

  • Add your PHP directory to your windows environment variable PATH. Go to the Advanced System Settings, Environment Variables and add your php path to the PATH variable C:\wamp\bin\php\php5.4.3.
  • Download PEAR, go to http://pear.php.net/go-pear.phar and download the PEAR file into your PHP directory.
  • Open up a new command prompt window and navigate to the php directory.
  • Run the php go-pear.phar file.

The above will start the install of PEAR, now we can install PHPUnit by using the PEAR installer.

  • Open up a new command prompt as administrator.
  • Run the command pear config-set auto_discover 1
  • Run the command pear install pear.phpunit.de/PHPUnit
  • Open up your php.ini file located in C:\wamp\bin\php\php5.4.3 and find the following lines

    ;***** Added by go-pear
    include_path=".;C:\wamp\bin\php\php5.4.3\pear"
    ;*****

  • Copy these files inside the apache php.ini file located C:\wamp\bin\apache\apache2.2.22\bin.
  • Restart WAMP

You will now have PHPUnit installed and can start writing your unit tests.

Writing Unit Tests

Before you start writing your unit tests you need to setup a test configuration file to tell PHPUnit where to look for your unit tests.

Create a new XML file and add the following code to your phpunit config file.

<phpunit backupGlobals="true"
         backupStaticAttributes="false"
         cacheTokens="false"
         colors="false"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         forceCoversAnnotation="false"
         mapTestClassNameToCoveredClassName="false"
         printerClass="PHPUnit_TextUI_ResultPrinter"
         processIsolation="false"
         stopOnError="false"
         stopOnFailure="false"
         stopOnIncomplete="false"
         stopOnSkipped="false"
         testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader"
         strict="false"
         verbose="false">
         <testsuites>
           <testsuite name="Test Title">
             <directory>/path/to/your/test/directory</directory>
             <file>/file/of/tests.php</file>
           </testsuite>
         </testsuites>
</phpunit>

There are different types of functions that you can use with PHPUnit.

  • function setUp() - The first thing that will run before your tests, should be used to setup any global variables.
  • function test*() - The test to run prefix the function with the word test.
  • function tearDown() - The last function to run on your tests, should be used as a cleanup.

Assertions

To verify if the values coming back from your functions are what you expect them to be PHPUnit comes with a number of assertions you can use to check the results.

Here is a list of assertions you can use with PHPUnit.

assertTrue($x) Fail if $x is false
assertFalse($x) Fail if $x is true
assertNull($x) Fail if $x is set
assertNotNull($x) Fail if $x not set
assertIsA($x, $t) Fail if $x is not the class or type $t
assertNotA($x, $t) Fail if $x is of the class or type $t
assertEqual($x, $y) Fail if $x == $y is false
assertNotEqual($x, $y) Fail if $x == $y is true
assertWithinMargin($x, $y, $m) Fail if abs($x – $y) < $m is false
assertOutsideMargin($x, $y, $m) Fail if abs($x – $y) < $m is true
assertIdentical($x, $y) Fail if $x == $y is false or a type mismatch
assertNotIdentical($x, $y) Fail if $x == $y is true and types match
assertReference($x, $y) Fail unless $x and $y are the same variable
assertClone($x, $y) Fail unless $x and $y are identical copies
assertPattern($p, $x) Fail unless the regex $p matches $x
assertNoPattern($p, $x) Fail if the regex $p matches $x
expectError($x) Swallows any upcoming matching error
assert($e) Fail on failed expectation object $e

Unit Test A Calculator

We are going to create a very basic calculator which will have 4 functions to add, subtract, divide and multiple. We are also going to create some unit tests with PHPunit to test that the calculator functions correctly.

In true test driven development style we start off by creating the tests for the calculator, we know the 4 main requirements we want the calculator to perform so we can start off by creating tests for these.

Create a new PHP file in your test directory and add the following code.

<?php
class CalculatorTests extends PHPUnit_Framework_TestCase
{
	/**
	 * Test to add two numbers
	 */
	public function testadd()
	{
	}
	/**
	 * Test to subtract two numbers
	 */
	public function testsubtract()
	{
	}
	/**
	 * Test to multiple two numbers
	 */
	public function testmultiple()
	{
	}
	/**
	 * Test to divide two numbers
	 */
	public function testdivide()
	{
	}
}
?>

We now have 4 tests that will run when you run this file with PHPUnit, but the tests don't do anything yet as we need to run the functions that are going to be added in the calculator class.

We know that all the functions are going to need to use the same calculator class so we can add this in the setUp function to define a class variable which is the calculator class.

private $calculator;
protected function setUp()
{
	require_once 'Calculator.php';
	$this->calculator = new Calculator();
}
protected function tearDown()
{
	$this->calculator = NULL;
}

We now have access to a calculator variable inside this calculatorTest class, which we can use to run a add method in the class.

This test will run the add method passing in two parameters and the return goes into a variable called $equals, we then use the assertEquals method to make sure that the $equals variable is what we expect it to be.

/**
 * Test to add two numbers
 */
public function testadd()
{
	$equals = $this->calculator->add( 1, 2 );
	$this->assertEquals(3, $equals);
}

When we run this test it will fail until we create the calculator class to make sure these all pass. But first lets complete the tests on all of the methods for the calculator class.

Here is the complete CalculatorTests class.

<?php
class CalculatorTests extends PHPUnit_Framework_TestCase
{
	private $calculator;
	protected function setUp()
	{
		require_once 'calculator.php';
		$this->calculator = new Calculator();
	}
	protected  function tearDown()
	{
		$this->calculator = NULL;
	}
	/**
	 * Test to add two numbers
	 */
	public function testadd()
	{
		$equals = $this->calculator->add( 1, 2 );
		$this->assertEquals(3, $equals);
	}
	/**
	 * Test adding string
	 */
	public function testaddString()
	{
		$equals = $this->calculator->add( "1", "2" );
		$this->assertEquals(3, $equals);
	}
	/**
	 * Test adding null
	 */
	public function testaddNull()
	{
		$equals = $this->calculator->add( NULL, NULL );
		$this->assertFalse($equals);
	}
	/**
	 * Test to subtract two numbers
	 */
	public function testsubtract()
	{
		$equals = $this->calculator->subtract( 2, 1 );
		$this->assertEquals(1, $equals);
	}
	/**
	 * Test to subtract two numbers
	 */
	public function testsubtractString()
	{
		$equals = $this->calculator->subtract( "2", "1" );
		$this->assertEquals(1, $equals);
	}
	/**
	 * Test to subtract two numbers
	 */
	public function testsubtractNull()
	{
		$equals = $this->calculator->subtract( NULL, NULL );
		$this->assertFalse($equals);
	}
	/**
	 * Test to multiple two numbers
	 */
	public function testmultiple()
	{
		$equals = $this->calculator->multiple( 1, 2 );
		$this->assertEquals(2, $equals);
	}
	/**
	 * Test to multiple two numbers
	 */
	public function testmultipleString()
	{
		$equals = $this->calculator->multiple( "1", "2" );
		$this->assertEquals(2, $equals);
	}
	/**
	 * Test to multiple two numbers
	 */
	public function testmultipleNull()
	{
		$equals = $this->calculator->multiple( NULL, NULL );
		$this->assertFalse($equals);
	}
	/**
	 * Test to divide two numbers
	 */
	public function testdivide()
	{
		$equals = $this->calculator->divide( 10, 2 );
		$this->assertEquals(5, $equals);
	}
	/**
	 * Test to divide two numbers
	 */
	public function testdivideString()
	{
		$equals = $this->calculator->divide( "10", "2" );
		$this->assertEquals(5, $equals);
	}
	/**
	 * Test to divide two numbers
	 */
	public function testdivideNull()
	{
		$equals = $this->calculator->divide( NULL, NULL );
		$this->assertFalse($equals);
	}
}
?>

Calculator Class

With the tests all setup we can now start development on the Calculator class. This class is only going to have 4 methods for add, subtract, multiple and divide.

The following code is the full code that is going to be used for the Calculator and will make sure that all the tests are going to pass. Create a new PHP file called Calculator.php and add the following code.

<?php
class Calculator
{
	/**
	 * Test to add two numbers
	 *
	 * @param int $number1 First number
	 * @param int $number2 Second number
	 */
	public function add( $number1, $number2 )
	{
		if(!is_numeric($number1) || !is_numeric($number2))
		{
			return false;
		}
		return $number1 + $number2;
	}
	/**
	 * Test to subtract two numbers
	 *
	 * @param int $number1 First number
	 * @param int $number2 Second number
	 */
	public function subtract( $number1, $number2 )
	{
		if(!is_numeric($number1) || !is_numeric($number2))
		{
			return false;
		}
		return $number1 - $number2;
	}
	/**
	 * Test to multiple two numbers
	 *
	 * @param int $number1 First number
	 * @param int $number2 Second number
	 */
	public function multiple( $number1, $number2 )
	{
		if(!is_numeric($number1) || !is_numeric($number2))
		{
			return false;
		}
		return $number1 * $number2;
	}
	/**
	 * Test to divide two numbers
	 *
	 * @param int $number1 First number
	 * @param int $number2 Second number
	 */
	public function divide( $number1, $number2 )
	{
		if(!is_numeric($number1) || !is_numeric($number2))
		{
			return false;
		}
		return $number1 / $number2;
	}
}
?>

Now if you run your unit tests you will see that all of the tests will now pass and you have tested the return of the functions even when you pass in different types of parameters.

This is how you use test driven development with PHP hopefully you will be able to use this on your future projects.



Published at DZone with permission of Paul Underwood, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)