Saturday, November 28, 2009

Introduction to TDD with PHPUnit


What is TDD?

Test Drive Development [TDD] is an approach to development which combines test-first development where you write a test before you write just enough production code to fulfill that test and refactoring. So basically you first decide the functions or sections of your code you want to test. Then write test for these functions for different scenarios with expected values. Then write the implement the functions while checking if they meet the different scenarios you captured in the tests, if not refactoring is done till all tests have passed.

Why use TDD?

  • by focusing on the different test cases first, developer(s) focuses on the functionality more than the implementation
  • it allows the developer to focus on a small section of the implementations at a time till its working as expected
  • can lead to more modularized, flexible, and extensible code
  • no more code is written than necessary to pass a failing test case, automated tests tend to cover every code path
  • while it is true that more code is required with TDD than without TDD because of the unit test code, total code implementation time is typically shorter

TDD Example with PHP

There are many ways in which one can implement a TDD. In this example, we will create a simple calculator with the four basic operations that is (add, subtract, multiply and divide).
But after looking around and looking back to my exprience, it looks like there is one way that most developers agree on:-
  1. design your class/API
  2. create a test suite
  3. run the test, and in this case all tests should fail
  4. implement the class
  5. run the test, an this time some test will pass and some will fail
  6. fix failures or errors
  7. repeat 5 and 6 till all tests have passed

Download Source Code

Designing the class/API

This basically mean designing the skeleton of the class, so the class body with its functions signatures without the actually implementation
/* === calculator.php === */
class Calculator{
/**
* Constructor
* NOTE: if you are using function __constructor() can also be used for older version of PHP
* @return unknown_type
*/
function Calculator(){

}

/**
* add two numbers (a + b)
* @param $a an integer
* @param $b an integer
* @return a + b or null otherwise
*/
function add($a, $b){

}

/**
* subtact b from a numbers (a - b)
* @param $a an integer
* @param $b an integer
* @return a - b or null otherwise
*/
function subtract($a, $b){

}

/**
* multipy a and b numbers (a x b)
* @param $a an integer
* @param $b an integer
* @return a x b or null otherwise
*/
function  multiply($a, $b){

}

/**
* divide a by b numbers (a / b)
* @param $a an integer
* @param $b an integer
* @return a / b or null otherwise
*/
function  multiply($a, $b){

}
}

Creating the test class

To test a function there is two (or more depending on the test used) things that are  needed, value returned by the function and the values expected. Then using a once of the many assert functions to establish if the test has passed or not. Look at the comments on the code.

/* === calculatortest.php === */
require_once 'calculator.php';
require_once 'PHPUnit.php';

class CalculatorTest extends PHPUnit_TestCase{
 /**
  * Constructor
  * NOTE: if you are using function __constructor() can also be used for older version of PHP
  * @return unknown_type
  */
 var $calc;

 function CalculatorTest($name){
  $this->PHPUnit_TestCase($name);
 }

 /**
  * Used to set up and default values
  *
  * (non-PHPdoc)
  * @see PHPUnit/PHPUnit_TestCase#setUp()
  */
 function setUp(){
  // creating the Calculator object
  $this->calc = new Calculator();
 }

 /**
  * Used to destroy values that where set in the setUp()
  *
  * (non-PHPdoc)
  * @see PHPUnit/PHPUnit_TestCase#tearDown()
  */
 function tearDown(){
  // destroying the  calculator object 
  unset($this->calc);
 }

 /**
  * Testing add two numbers (a + b)
  */
 function testAdd(){
  $actual = $this->calc->add(2,3);
  $expected = 5;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

 /**
  * Testing subtact b from a numbers (a - b)
  */
 function testSubtract(){
  $actual = $this->calc->subtract(5,2);
  $expected = 3;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

 /**
  * Testing multipy a and b numbers (a x b)
  */
 function  testMultiply(){
  $actual = $this->calc->multiply(5,2);
  $expected = 10;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

 /**
  * Testing divide a by b numbers where b=0
  */
 function  testDivideByZero(){
  $actual = $this->calc->divide(3,0);
  $expected = NULL;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

 /**
  * Testing divide a by b numbers (a / b)
  */
 function  testDivide(){
  $actual = $this->calc->divide(10,2);
  $expected = 5;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }
}

Write the Testing

Now we need to write the code to actual run the code
/* ==== test.php ==== */

require_once 'calculatortest.php';
require_once 'PHPUnit.php';

/* the value passed duing the instantiating must be the name of the test class */
$suite  = new PHPUnit_TestSuite("CalculatorTest");
/*rin the test*/
$result = PHPUnit::run($suite);
/*dispay the result */
echo $result->toString();

Running the test

Start up your terminal and run the test.php with php or if run it through your apache and view the result on the browser
eferuzi@feruzi /v/w/phpunit> php5 test.php
TestCase CalculatorTest->testDivideByZero() passed
TestCase CalculatorTest->testAdd() failed: expected 5, actual  in /var/www/phpunit/calculatortest.php:56
TestCase CalculatorTest->testSubtract() failed: expected 3, actual  in /var/www/phpunit/calculatortest.php:66
TestCase CalculatorTest->testMultiply() failed: expected 10, actual  in /var/www/phpunit/calculatortest.php:76
TestCase CalculatorTest->testDivide() failed: expected 5, actual  in /var/www/phpunit/calculatortest.php:96
As you can see we have one passing because the functions are not returning anything at the moment and that test checks if NULL is returned.

Implementations of the Calculator

So now we add implementation into the functions in the Calculator class

/* === calculatortest.php === */


require_once 'calculator.php';
require_once 'PHPUnit.php';

class CalculatorTest extends PHPUnit_TestCase{
 /**
  * Constructor
  * NOTE: if you are using function __constructor() can also be used for older version of PHP
  * @return unknown_type
  */
 var $calc;

 function CalculatorTest($name){
  $this->PHPUnit_TestCase($name);
 }

 /**
  * Used to set up and default values
  *
  * (non-PHPdoc)
  * @see PHPUnit/PHPUnit_TestCase#setUp()
  */
 function setUp(){
  // creating the Calculator object
  $this->calc = new Calculator();
 }

 /**
  * Used to destroy values that where set in the setUp()
  *
  * (non-PHPdoc)
  * @see PHPUnit/PHPUnit_TestCase#tearDown()
  */
 function tearDown(){
  // destroying the  calculator object
  unset($this->calc);
 }

 /**
  * Testing add two numbers (a + b)
  */
 function testAdd(){
  $actual = $this->calc->add(2,3);
  $expected = 5;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

 /**
  * Testing the add method with wrong or none numerical input
  */
 function testAddInvalidValues(){
  $actual = $this->calc->add('ugali',3);
  //checking if value returned value is what we expected
  $this->assertNull($actual);
 }


 /**
  * Testing subtact b from a numbers (a - b)
  */
 function testSubtract(){
  $actual = $this->calc->subtract(5,2);
  $expected = 3;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

 /**
  * Testing multipy a and b numbers (a x b)
  */
 function  testMultiply(){
  $actual = $this->calc->multiply(5,2);
  $expected = 10;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

 /**
  * Testing divide a by b numbers where b=0
  */
 function  testDivideByZero(){
  $actual = $this->calc->divide(3,0);
  $expected = NULL;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

 /**
  * Testing divide a by b numbers (a / b)
  */
 function  testDivide(){
  $actual = $this->calc->divide(10,2);
  $expected = 5;
  //checking if value returned value is what we expected
  $this->assertEquals($expected, $actual);
 }

Re-run the test

Re-run the test again and this time you should see some more passes if not refactor your code till you get all passes.

eferuzi@feruzi /v/w/phpunit> php5 test.php
TestCase CalculatorTest->testAdd() passed
TestCase CalculatorTest->testAddInvalidValues() passed
TestCase CalculatorTest->testSubtract() passed
TestCase CalculatorTest->testMultiply() passed
TestCase CalculatorTest->testDivideByZero() passed
TestCase CalculatorTest->testDivide() passed

Download Source Code

More information:-

  • TDD:
    • http://en.wikipedia.org/wiki/Test-driven_development
    • http://www.extremeprogramming.org/rules.html
  • PHPUnit:
    • http://www.phpunit.de/
Do not hesitate to ask me questions.

No comments:

Post a Comment