unit testing

Testing for PHP

PHP does not embed unit testing as well into development practice as with other languages like Java and Perl. Even with PHP, however, it is a good habit to get into. With unit testing and integration testing on hand, it is easier to track a project and monitor for inconsistencies between the different parts of the development work. Fortunately, there is an excellent testing framework for PHP: SimpleTest, available at http://simpletest.org/. It has more features in it than you could throw a stick at, and it works well and robustly. Meet-O-Matic now uses this as a key part of its development process, but other, larger, systems (such as Moodle) do too. However, for Meet-O-Matic, I wanted a rather better report than was available in the basic version. For example, I wanted to detect all syntax errors, not just crashing out when one was detected. And secondly, I wanted to have a set of tests, each classified in some way. To achieve all this, a few tweaks were needed. First of all, I subclassed SimpleReporter as follows:

<?php
require_once('simpletest/simpletest.php');

class 
StringReporter extends SimpleReporter {

    var 
$result "";
    var 
$_character_set 'ISO-8859-1';

    function 
getBlock() {
        return 
$this->result;
    }

    function 
paintHeader() {
        
$this->add("<h3>Testing module: $test_name</h3>\n");
    }

    function 
add($string) {
        
$this->result .= $string;
    }

    function 
paintFooter() {
        
$style = ($this->getFailCount() + () > "failblock" "passblock");
        
$this->add("<div class=\"$style\">");
        
$this->add($this->getTestCaseProgress() . "/" $this->getTestCaseCount());
        
$this->add(" test cases complete:\n");
        
$this->add("<strong>" $this->getPassCount() . "</strong> passes, ");
        
$this->add("<strong>" $this->getFailCount() . "</strong> fails and ");
        
$this->add("<strong>" $this->getExceptionCount() . "</strong> exceptions.");
        
$this->add("</div>\n");
    }

    function 
paintStart($test_name$size) {
        
parent::paintStart($test_name$size);
    }

    function 
paintEnd($test_name$size) {
        
parent::paintEnd($test_name$size);
    }

    function 
paintPass($message) {
        
parent::paintPass($message);
    }

    function 
paintFail($message) {
        
parent::paintFail($message);
        
$this->add("<span class=\"fail\">Fail</span>: ");
        
$breadcrumb $this->getTestList();
        
array_shift($breadcrumb);
        
$this->add(implode(" -> "$breadcrumb));
        
$this->add(" -> " $this->_htmlEntities($message) . "<br />\n");
    }

    function 
paintError($message) {
        
parent::paintError($message);
        
$this->add("<span class=\"fail\">Exception</span>: ");
        
$breadcrumb $this->getTestList();
        
array_shift($breadcrumb);
        
$this->add(implode(" -> "$breadcrumb));
        
$this->add(" -> <strong>" $this->_htmlEntities($message) . "</strong><br />\n");
    }

    function 
paintSkip($message) {
        
parent::paintSkip($message);
        
$this->add("<span class=\"pass\">Skipped</span>: ");
        
$breadcrumb $this->getTestList();
        
array_shift($breadcrumb);
        
$this->add(implode(" -> "$breadcrumb));
        
$this->add(" -> " $this->_htmlEntities($message) . "<br />\n");
    }

    function 
_htmlEntities($message) {
        return 
htmlentities($messageENT_COMPAT$this->_character_set);
    }
}
?>

All this does is generate a string block of HTML rather than a file - because of this we can now glue several of these together into a single page display. The next page runs the tests in all files ending _test.php, and then combines the results into a single page. It assumes that the class in each file is called TestOf..., where the ... is filled from the name of the file (minus _test.php). Most of the rest is styling.

<?php
require_once('simpletest/unit_tester.php');
require_once(
'StringReporter.php');

$tests glob("*_test.php");

?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>All tests&lt;/title>
<meta http-equiv="Content-Type" content="text/html">
<style type="text/css">
body { font-family: Verdana, sans-serif; font-size: 1.8ex }
.failblock { padding: 8px; margin-top: 1em; background-color: red; color: white; }
.passblock { padding: 8px; margin-top: 1em; background-color: green; color: white; }
.fail { background-color: inherit; color: red; }
.pass { background-color: inherit; color: green; }
pre { background-color: lightgray; color: inherit; }
</style>
</head>
<body>
<?php
foreach ($tests as $test) {
    
$name preg_replace("/_test.php$/""", );
    require(
$test);
    
    
$suite = &new TestSuite($name);
    
$instance = eval("return new TestOf$name();");
    
$suite->addTestCase($instance);

    
$reporter = new StringReporter();
    
$suite->run($reporter);
    echo 
$reporter->getBlock();
}
?>
</body>
</html>

OK, so this is a bit of a hack, but it does work, and the results are a bit better broken down than having a large single block of tests (which is done, for example, by Moodle). Even so, SimpleTest is great: it does everything you are likely to need, although you may well want to subclass it from time to time.
Syndicate content