Unit tests with PHPUnit and config.php

Unit tests with PHPUnit and config.php

by Marcel Heusinger -
Number of replies: 6
Hello,

try to create some PHPUnit test cases for a new Moodle resource type I implemented. Because I use some functions from the weblib.php library, I had to include the config.php in order to get $CFG set properly. One line in the config.php calls the lib/setup.php that initializes some other required variables and so on.
My installation works fine as I can do anything like adding courses, and materials.
But my test case doesn't work because the setup.php dies with an fatal error: $CFG->wwwroot not set. But it is set in my config.php to lines before the setup.php is called.
Does anyone have an idea how to fix this or an example of an working test case that calls some moodle lib functions. Any help would be appreciated.

Thanks,
Marcel
Average of ratings: -
In reply to Marcel Heusinger

Re: Unit tests with PHPUnit and config.php

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Even though you may be familiar with PHPUnit, you might want to try using SimpleTest, which we have integrated with Moodle. Try going to Admin -> Reports -> Unit tests. And see Development:Unit_tests for instructions.

I expect that PHPUnit and SimplteTest are pretty similar, so it should be easy for you to make the switch.

In reply to Tim Hunt

Re: Unit tests with PHPUnit and config.php

by Marcel Heusinger -
Thank you for your fast reply.
After skimming thorugh the link you post, it is indeed very similiar and it doesn't have the disadvantage of not working with global variables.
Thank you very much for your help.
Marcel
In reply to Tim Hunt

Re: Unit tests with PHPUnit and config.php

by Marcel Heusinger -
Hello Tim,

once again thanks for your advice. As I was missing the contains asserts from PHPUnit i worked out something similiar for SimpleTest. As you are one of the developers related to this Moodle part, I wondered if you and the community respectively are interested in it. Maybe you want to add it to the source code.
I added the code below to following two files:
1. lib/simpletestlib/expectation.php
2. lib/simpletestlib/unit_tester.php

Code added to unit_tester.php

function assertContainsKey ($array, $key, $message = '%') {
return $this->assert(
new ContainsKeyExpectation($array), $key, $message);
}
function assertContainsValue ($array, $value, $message = '%') {
return $this->assert(
new ContainsValueExpectation($array), $value, $message); }
function assertContainsKeyNot ($array, $key, $message = '%') {
return $this->assert(
new ContainsKeyNotExpectation($array), $key, $message);
}
function assertContainsValueNot ($array, $value, $message = '%') {
return $this->assert(
new ContainsValueNotExpectation($array), $value, $message);
}


Further I added this code to the expectation.php:
/**
* Test for a key contained in an array.
* @package SimpleTest
* @subpackage UnitTester
*/
class ContainsKeyExpectation extends SimpleExpectation {
var $_array;

/**
* Sets the array required for the comparison.
* @param array $array Array used for testing.
* @param string $message Customised message on failure.
* @access public
*/
function ContainsKeyExpectation($array, $message = '%s') {
$this->SimpleExpectation($message);
$this->_array = $array;
}

/**
* Tests the expectation. True if it matches the
* held value.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
$keys = array_keys ($this->_array);
foreach ($keys as $key) {
if ($key === $compare) {
return true;
}//if
}//foreach
return false;
}

/**
* Returns a human readable test message.
* @param string $compare Key being tested.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
return $this->_containsKeyMessage($compare);
} else {
return $this->_containsKeyNotMessage($compare);
}
}

/**
* Creates a the message for the containing case.
* @param string $compare Key being tested.
* @access private
*/
function _containsKeyMessage($compare) {
return "Array contains key " . $compare;
}

/**
* Creates a the message for the not containing case.
* @param string $compare Key being tested.
* @access private
*/
function _containsKeyNotMessage($compare) {
return "Array does not contain key " . $compare;
}
}

/**
* Test for a key not contained in an array.
* @package SimpleTest
* @subpackage UnitTester
*/
class ContainsKeyNotExpectation extends ContainsKeyExpectation {
var $_array;

/**
* Sets the array required for the comparison.
* @param array $array Array used for testing.
* @param string $message Customised message on failure.
* @access public
*/
function ContainsKeyNotExpectation($array, $message = '%s') {
$this->ContainsKeyExpectation($array, $message);
}

/**
* Tests the expectation. True if it matches the
* held value.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return ! parent::test($compare);
}

/**
* Returns a human readable test message.
* @param string $compare Key being tested.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
return $this->_containsKeyMessage($compare);
} else {
return $this->_containsKeyNotMessage($compare);
}
}
}

/**
* Test for a value contained in an array.
* @package SimpleTest
* @subpackage UnitTester
*/
class ContainsValueExpectation extends SimpleExpectation {
var $_array;

/**
* Sets the array required for the comparison.
* @param array $array Array used for testing.
* @param string $message Customised message on failure.
* @access public
*/
function ContainsValueExpectation($array, $message = '%s') {
$this->SimpleExpectation($message);
$this->_array = $array;
}

/**
* Tests the expectation. True if it matches the
* held value.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
$keys = array_values ($this->_array);
foreach ($keys as $key) {
if ($key === $compare) {
return true;
}//if
}//foreach
return false;
}

/**
* Returns a human readable test message.
* @param string $compare Key being tested.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
return $this->_containsValueMessage($compare);
} else {
return $this->_containsValueNotMessage($compare);
}
}

/**
* Creates a the message for the containing case.
* @param string $compare Value being tested.
* @access private
*/
function _containsValueMessage($compare) {
return "Array contains value " . $compare;
}

/**
* Creates a the message for the not containing case.
* @param string $compare Value being tested.
* @access private
*/
function _containsValueNotMessage($compare) {
return "Array does not contain value " . $compare;
}
}


/**
* Test for a value not contained in an array.
* @package SimpleTest
* @subpackage UnitTester
*/
class ContainsValueNotExpectation extends ContainsValueExpectation {
/**
* Sets the array required for the comparison.
* @param array $array Array used for testing.
* @param string $message Customised message on failure.
* @access public
*/
function ContainsValueNotExpectation($array, $message = '%s') {
$this->ContainsValueExpectation($array, $message);
}

/**
* Tests the expectation. True if it matches the
* held value.
* @param string $compare Comparison value.
* @return boolean True if correct.
* @access public
*/
function test($compare) {
return ! parent::test($compare);
}

/**
* Returns a human readable test message.
* @param string $compare Key being tested.
* @return string Description of success
* or failure.
* @access public
*/
function testMessage($compare) {
if ($this->test($compare)) {
return $this->_containsValueMessage($compare);
} else {
return $this->_containsValueNotMessage($compare);
}
}
}

I tested the new asserts with the following test function:
public function test_Expectations () {

$array = array ('foo' => 'bar', 100 => 0, 'color' => 'red',
'elem' => array (0 => 1));

$this->assertContainsValue ($array, 'bar');
$this->assertContainsValueNot ($array, 'blue');
$this->assertContainsKey ($array, 100);
$this->assertContainsKeyNot ($array, 'colour');
$this->assertContainsKey ($array, 'elem');
$this->assertContainsValue ($array, array (0 => 1));
}


Hope you like it and it is ok that I posted all of it here. If you want I can send you the source code as well
Thanks,
Marcel
In reply to Marcel Heusinger

Re: Unit tests with PHPUnit and config.php

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
The simpletestlib folder is supposed to be a straight copy of the simpletest release, so it is better not to change it (although we have had to make one or two changes)

Instead we try to add moodle-specific customisations in the simpletestlib.php file.

So, I would put the extra assertion definitions in there.

Then, you can always use the new assertions directly, using

$this->assert(new ContainsValueExpectation($expected), $actual);

Or we could make a subclass of UnitTestCase (MoodleUnitTestCase, or TestCaseForPeopleWhoPreferPHPUnit or something) that adds the extra assertion functions.


However, I tend to prefer more declarative. For example, I have made CheckSpecifiedFieldsExpectation which you use like

$expected = new stdClass;
$expected->name = 'Fred';
$expected->score = '100%';
$this->assert(new CheckSpecifiedFieldsExpectation($expected), $actual);

That will pass if $actual is an object with those two fields that have those two values. And it will ignore any other fields on $actual.

(Moodle tends to use struct-like objects more often than it uses arrays.)

Anyway, I am supposed to be on holiday now, so please could you put your suggestion and your code in an new feature request in http://tracker.moodle.org/. Feel free to assign it to me, and I will look at it when I get back.
In reply to Tim Hunt

Re: Unit tests with PHPUnit and config.php

by Marcel Heusinger -
Hello Tim,

I added the feature request to the Moodle Tracker. As I was not allowed to assign you to the request, I am going to post the link right here: http://tracker.moodle.org/browse/MDL-19137
Thanks for your help.
Kind regards,
Marcel
In reply to Marcel Heusinger

Re: Unit tests with PHPUnit and config.php

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Thanks. It got assigned to Nico by default, and he is a good person for it to be assigned to.

By the way, you need only type the bug id (for example MDL-19137) and it will automatically be made into a link to the tracker. (That is a custom text filter at work.)