Sunday, April 15, 2012

Test Driven Development with PHPUnit

       TDD (Test Driven Development) concept is common practice of most of the companies which involve with the long time projects. It helps to keep the static behavior of functions over the development period. The functions are implemented after implementing test methods (unit tests) for that function. After implementing the function, run the tests and if any test is failing, again modify the function. This process repeats until the all tests are passing.

                       Test method is a function which check whether the specific behavior of a function is correct. Any function can have at least two test methods. one positive test and one negative test. Most probably a function may have many test methods. The class in which all the test methods are placed is called Test Case. By setting up a test suite for this class, the all tests methods can be run at once.

         In long time projects like open source projects, the functions are frequently modified by the developers during a long time. If the modified function is not given the previous behavior of that function, the output for other places will be going wrong. It can be cause to failures. By writing tests this problem can be reduced. Before and after modifying the test cases, by running the tests, developer can be verify the function behave correctly. As well by going through the test cases helps even understand what actually function does.

      To follow this TDD concept with Java, JUnit framework can be used. As well with PHP, PHPUnit framework can be used. Let's do some experiments with PHPUnit with Ubuntu OS.

Install PHPUnit framework
$sudo apt-get install phpunit

Now create your test case as a sub class of 'PHPUnit_Framework_TestCase' having just one dummy method to verify we can forward without issue.

<?php

    require_once 'PHPUnit/Framework.php'; // installed file with phpunit framework which is located at : /usr/share/php/PHPUnit/

    class MyClassTest extends PHPUnit_Framework_TestCase {

        // This function just return true
        function testCheckAllWorksFine() {
            $this->assertTrue(true);
        }

    }

?>

Run this MyClassTest.php file as :
$phpunit MyClassTest.php

If the result is as following (Fig. 1), you can proceed the work with phpunit.
Fig. 1


Characters it returns :

.     : pass
F    : fail
E    : error


The functions starting from 'assert' is implemented in the framework. As well there are more useful functions we can use with this framework. You can find more assert functions by visiting http://www.phpunit.de/manual/3.6/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.

I have used following useful assert functions for this article :
assertTrue() : Check whether the parameter is True
assertFalse() : Check whether the parameter is False
assertEqual() : Match two results



Start work

1. Requirement : need a function which accept two strings and return the length of the most lengthy string, if both arguments are not strings return false

* First just declare your function properly with arguments
<?php
    class MyClass {   

        function getMaxLength ($str1, $str2) {    

        }

    }
?>

* Now start to write test methods,
a). Check the arguments
<?php

    require_once 'MyClass.php';
    require_once 'PHPUnit/Framework.php';

    class MyClassTest extends PHPUnit_Framework_TestCase {

        var $obj;

        // This function calls before any test merhod run
        function setUp() {
            $this->obj = new MyClass();
        }

        // Positive : Test getMaxLength for correct arguments
        function testGetMaxLengthForAtLeastOneIsString() {
            $paramArray = array("str1", 2);
            $result = $this->obj->getMaxLength($paramArray[0], $paramArray[1]);
            $this->assertEquals(4, $result);
        }

        // Negative : Test getMaxLength for non of arguments are string
        function testGetMaxLengthForNoStrings() {
            $paramArray = array(array(1,2), 12.3);
            $result = $this->obj->getMaxLength($paramArray[0], $paramArray[1]);
            $this->assertFalse($result);
        }

    }

?>


The setUp() will call automatically before running any test method. It can be used for create a fresh objects, load data for databases if using etc. All test methods must be start with 'test'. Using the function name which is going to use by specific test method regardless of the length of the test method name, will be increase the readability and understandability while reviewing later.


b). Check whether the function returns an integer
// Test getMaxLength for output integer for correct arguments
function testGetMaxLengthForCorrectReturnType() {
    $paramArray = array("str1", 2);
    $result = $this->obj->getMaxLength($paramArray[0], $paramArray[1]);
    $this->assertTrue(is_int($result));
}



c). Check whether is returns the length of the most lengthy string
// Test getMaxLength for Length of the lengthy string
function testGetMaxLengthForCorrectLength() {
    $paramArray = array("short_string", "lenghty_string");
    $result = $this->obj->getMaxLength($paramArray[0], $paramArray[1]);
    $this->assertEquals(14, $result);
}


Now the most needed test cases are implemented. From the above test methods, some scenarios like check the function return length of the string for arguments having only one string, is already covered. Now just run the test and see most of them are failing. Its time to start implement the actual function in a way that all te test cases are passing.


Function can be written as :
function getMaxLength($str1, $str2) {   

    if(is_string($str1) || is_string($str2)) {
        if(is_string($str1) && is_string($str2)) {
            $max = (strlen($str1) >= strlen($str2)) ? strlen($str1) : strlen($str2);
        } elseif (is_string($str1)) {
            $max = strlen($str1);
        } else {
            $max = strlen($str2);
        }
        return $max;
    }
    return false;

}


Now again run the test. See whether all the test are passing or not. If not your function may have some issues. Improve the function and again run the test. Do this process until all the test are passing.

The example we discussed was very easy one. But in real applications we have use more complex algorithms and tactics dealing with databases. To check the functionality of the data transaction functions, we can use a test database with some preloaded data. Inside the setUp() function we can create tables and load the data to the test database. As well using the function tearDown() which automatically call after execution of any test method, we can clean the data from the database.

Sometimes applications with MVC architecture have two or more separate layers in Data Layer. In such kind of situation, functions need to test independent from their layers. Lets assume in our application there are two layers in Data Layer. One is data transaction layer which directly deal with the database and other one is data processing layer which process data before or after deal with database through data transaction layer. So, while writing test for a function in data process layer which get some data by calling a function in data transaction layer, we should verify that the correct data set is returned from the function in data transaction layer. To do this we can use Moc Objects.

Lets go through one example.
* getStrings() method in DataTransaction class return an array with two strings (No need of even declare this class)

* getProcessedResult() method in DataProcess class, get the array of two strings by calling the above mentioned function (getStrings()), and return the length of the most lengthy string out of them. This class contain getter and setter methods for use DataTransaction class.
(Declare the getProcessedResult() function and implement getter and setter methods)

<?php

    class DataProcess {

        private $dataTransaction;

        function getDataTransaction() {
            if(is_null($this->dataTransaction)) {
                return new DataTransaction();
            }
            return $this->dataTransaction;
        }

        function setDataTransaction($dataTransaction) {
            $this->dataTransaction = $dataTransaction;
        }
  
        function getProcessedResult() {
    
        }

    }

?>

* Now write the test class using the Moc Object of DataTransaction class

<?php

    require_once 'DataProcess.php';
    require_once 'PHPUnit/Framework.php';

    class TestMoc extends PHPUnit_Framework_TestCase {

     var $dataProcess;

     // This function calls before any test merhod run
     function setUp() {
                $this->dataProcess = new DataProcess();
     }

     public function testGetProcessedResult() {

         // Create a Mock Object for the DataTransaction class
         // mocking only the getStrings() method.
         $dataTransaction = $this->getMock('DataTransaction', array('getStrings'));
  
         // Set up the expectation for the getStrings() method
         // to be called only once and with no arguments
         // and return value is array("abc", "abcde")
         $dataTransaction->expects($this->once())
                         ->method('getStrings')
                         ->with()
                         ->will($this->returnValue(array("abc", "abcde")));
  
         // Set the mocked DataTransaction object to 
            // DataProcess object using setter
         $this->dataProcess->setDataTransaction($dataTransaction);
  
         // Call the getProcessedResult() method on the $dataProcess object
            // which we expect to call the mocked DataTransaction object's
         // getStrings() method with no arguments.
         $this->assertEquals(5, $this->dataProcess->getProcessedResult());

     }

    }

?>

Here, even we didn't implement (even declare) the DataTransaction class, we could run the test using Moc Object. So that we can test functions independent from the layer it contains and other needed functions.


Setting up test suite

Test suit can be used to mange which test cases should run. We can run even all the test methods in several test cases at once. Test suite can be create as following example code snippet.

<?php

    require_once 'PHPUnit/Framework.php';

    class AllTests {

        public static function suite() {

            $suite  = new PHPUnit_Framework_TestSuite("PHPUnit");
            // Add needed test cases
            $suite->addTestFile("MyTestClass1.php");
            $suite->addTestFile("MyTestClass2.php");

            return $suite;

        }

    }

?>


With PHPUnit framework the percentage of the functions which covers from the unit tests can be obtained as a coverage report. Following command can be used to obtain a coverage report. (probably you need to install xDebug : http://xdebug.org/docs/install)

$phpunit --coverage-html ./report AllTests.php

From this report we can find out the uncovered code segments. Some of code segments in MyClass.php has not covered from the unit tests. To cover them again test can be implemented.

So, this is a basic idea of how to follow test driven development with PHPUnit framework. Hope this helpful to you.

1 comment: