moodle

Open Comment

Open Comment is an open source feedback tool, designed to plug into the Moodle open source virtual learning environment. It consists of a set of interlocking components, including:

  • A feedback engine
  • A Moodle question type
  • A graphical interface to test questions

You can test the graphical interface using the following URL, which provides a Java web start (JNLP) file. If you have a reasonably modern Java installed, you should simply be able to double-click this to install and run the program.

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.

Moodle keeps rolling on

I've been working with some of our internal players, trying to help keep some developments going on embedding Moodle within my workplace. We're a large (~10000 students) higher education institution, and like most other higher education institutions, we have a lot of existing systems and practices that we need to work with. The challenge is not technical, it is social - trying to encourage people to work within a slightly new framework.

Syndicate content