Friday, 5 October 2012

Getting up & running with SimpleTest (1 of 2)

Today we are going to work on something that I know every developer loves.
Unit-Tests! Well, you will once you get a handle on them. For this we are going to use the test framework SimpleTest from simpletest.org

Download and install SimpleTest

Download the current release version SimpleTest 1.1.0 *as of this writing. Then extract it into the directory that your web server can see. I unzipped it into C:/xampp/htdocs/tests/simpletest.

You can also install it a Eclipse Plugin.
Choose Help->Install New Software.. and type in "http://simpletest.org/eclipse/".


1.Creating A Test Case

I'd like to use an example to explain how to create a test case and how to use it to test a php file. The test target file named log.php which is used to create a log file.
Firstly, I create a empty directory as the root directory. Then I move the simpletest(the library folder) into the root directory. After that, I create two folders. One is named classes which contains the test target php file. Another one is temp where the log files will be created.
Next is the code in log.php.
<-- log.php --> 
<?php
class Log{
   var $_file_path;
   function Log($file_path){
      $this->_file_path = $file_path;
   }
   function message($message){
      $file=fopen($this->_file_path,'a');
      fwrite($file,$message."\n");
      fclose($file);
   }
}

The log_test.php is the test case which is used to test log.php. I put this file in the same folder with simpletest library.
The autorun.php is used to automatically run the test process. And you need to include the target test file in order to test each function of it. 
<-- log_test.php -->
require_once ('simpletest/autorun.php');
require_once ('classes/log.php'); 
In this part, there are four functions. The constructor sets the test name. The setUp and tearDown functions are used to create and delete the log file respectively. The getFileLine function is used to read the message in the file. 
<-- log_test.php Continue... -->
class TestOfLogging extends UnitTestCase{    
    function __construct(){
        parent::__construct('Log class test');
    } 
    function setUp(){
        @unlink('temp/test.log'); 
     //if setUp at the function beginning, then the test.log will be written in temp/ floder.
    }
    function tearDown(){
        @unlink('temp/test.log'); 
     // if call tearDown on the bottom of the function again, the test.log will be deleted.
    }
    function getFileLine($filename,$index){
        $messages=file($filename);
        return $messages[$index];    
    }
The next two functions are the actual testing functions.
The testCreatingNewFile function is used to test whether the test.log file created or not. The assertTrue() and assertFalse() are the library functions that are defined in the unit_tester.php. There are also many other assert functions in the simpletest library. They are listed below. Those assert functions are used to report the assertion and display a message. The messages should be set as the last parameter for every assert function.
<-- log_test.php Continue...  -->
    function testCreatingNewFile(){
        $log=new Log('temp/test.log');
        $this->assertFalse(file_exists('temp/test.log'),'No file created'); 
       // if the file exists, the assertion will be fail. Otherwise, it will be true.
        $log->message('Should write this to a file');                    
       // create the test.log and write message into it.
        $this->assertTrue(file_exists('temp/test.log'),'File created');  
        // if the file exists, the assertion will be true. Otherwise, it will be fail.
    }
The testAppendingToFile function is testing the contents of test.log.
<-- log_test.php Continue... -->
    function testAppendingToFile(){    
        $log=new Log('temp/test.log');
        $log->message('Test line 1');   // write the message into test.log
        $this->assertPattern('/Test line 1/', $this->getFileLine('temp/test.log',0)); //Using getFileLine() to read first message in test.log. 
                                                                                              //If the pattern in first parameter match with the second,
                                                                                              //the assertion will be true.  
        $log->message('Test line 2');        
        $this->assertPattern('/Test line 2/', $this->getFileLine('temp/test.log',1));    
    }
}
Next is to execute the test.
<-- log_test.php Continue... -->
} //end the class

$test = new TestOfLogging(); //instance
$test->run(new HtmlReporter()); // HtmlReporter is used to show out the result.
?>
If the test succeed, you should see the test name, the green bar with test results.

2.Creating A Test Suite

The first example showed how to create a single test case. Although you can define multiple test functions in one test php file, it is not enough. Next I would like to introduce how to make a test suite that can run multiple test php files one by one.
Firstly, I create a target test file named clock.php. It can return a timestamp. And I put it into the classes folder. Code is on below.
<-- clock.php -->
<?php 
class Clock{
    var $_offset;    
    function __construct(){
        $this->_offset=0;    
    }
    function now(){             // return a timestamp
        return time()+$this->_offset;
    }

    function advance($offset){  // change the timestamp;    
       $this->_offset+=$offset;
    }
}
Next, I would like to do some tests to the clock.php. Create the test case in the new file clock_test.php and leave it in the root directory.
<-- clock_test.php -->
<?php
    require_once('simpletest/autorun.php');
    require_once('classes/clock.php');

    class TestOfClock extends UnitTestCase {
        function TestOfClock() {
            parent::__construct('Clock test');
        }
        function testClockTellsTime() {
            $clock = new Clock();
            $this->assertEqual($clock->now(), time(), 'Now is the right time'); //test return time
        }
        function testClockAdvance() {
        }
    }
I have created the second test case now. Next I would like to make a test suite to run log_test.php and clock_test.php.
Create a new file named all_test.php. And put it in the root directory. Write code below into all_test.php
<-- all_test.php -->
<?php 
require_once('simpletest/autorun.php');

class AllTests extends TestSuite{
    function AllTests(){                          //you can also write as function __construct()
        $this->TestSuite('All tests here');   // and parent::__construct('All tests here');

        // if you have multiple test files, use addFile() function to add them.
        $this->addFile('log_test.php');
        $this->addFile('clock_test.php');
    }
}
Then, I can remove the "require_once('simpletest/autorun.php')" from log_test.php and clock_test.php. Also remove "$test = new TestOfLogging();" and "$test->run(new HtmlReporter());" from log_test.php.
Now, I can only run the all_test.php to run all the test files.

3.Show pass message

According the first two examples, we can see if the test succeed, we only get the numbers of test cases, passes, fails and exceptions. Next code is used to print out the details of passes information. I write it into the file named show_passes.php, and leave it in the root directory. 
<-- show_passes.php -->
<?php
require_once ('simpletest/reporter.php');

class ShowPasses extends HtmlReporter{
    function __construct(){
        parent::__construct();        
    }
    function paintPass($message){
        parent::paintPass($message);
        print "<span class=\"pass\">Pass</span>:";
        $breadcrumb = $this->getTestList();
        array_shift($breadcrumb);
        print implode("-&gt;", $breadcrumb);
        print "-&gt;$message<br />\n";
    }
}

Then go back to the all_test.php, and add "require_once('show_passes.php');" at the beginning, and add "$this->run(new ShowPasses());" at the bottom of function AllTests(). After that, if you run the test again, the passes information should be shown.

Want more? Part 2