Taking control of testing

In order to test a code module you need very tight control of its environment. If anything can vary behind the scenes, for example a configuration file, then this could cause the tests to fail unexpectedly. This would not be a fair test of the code and could cause you to spend fruitless hours examining code that is actually working, rather than dealing with the configuration issue that actually failed the test. At the very least your test cases get more complicated in taking account the possible variations.

Controlling time

There are often a lot of obvious variables that could affect a unit test case, especially in the web development environment in which PHP usually operates. These include database set up, file permissions, network resources and configuration amongst others. The failure or misinstall of one of these components will break the test suite. Do we add tests to confirm these components are installed? This is a good idea, but if you place them into code module tests you will start to clutter you test code with detail that is irrelavent to the immediate task. They should be placed in their own test group.

Another problem, though, is that our development machines must have every system component installed to be able to run the test suite. Your tests run slower too.

When faced with this while coding we will often create wrapper versions of classes that deal with these resources. Ugly details of these resources are then coded once only. I like to call these classes "boundary classes" as they exist at the edges of the application, the interface of your application with the rest of the system. These boundary classes are best simulated during testing by simulated versions. These run faster as well and are often called "Server Stubs" or in more generic form "Mock Objects". It is a great time saver to wrap and stub out every such resource.

One often neglected factor is time. For example, to test a session time-out coders will often temporarily set the session time limit to a small value, say two seconds, and then do a sleep(3) and assert that the session is now invalid. That adds three seconds to your test suite and is usually a lot of extra code making your session classes that maleable. Far simpler is to have a way to suddenly advance the clock. To control time.

A clock class

Again we will design our clock wrapper by writing tests. Firstly we add a clock test case to our tests/all_tests.php test suite...

<?php
if (! defined('SIMPLE_TEST')) {
    define('SIMPLE_TEST', 'simpletest/');
}
require_once(SIMPLE_TEST . 'autorun.php');
require_once('log_test.php');
require_once('clock_test.php');

$test = &new TestSuite('All tests');
$test->addTestCase(new TestOfLogging());
$test->addTestCase(new TestOfClock());
$test->run(new HtmlReporter());
?>
Then we create the test case in the new file tests/clock_test.php...
<?php
require_once('../classes/clock.php');

class TestOfClock extends UnitTestCase {
    function TestOfClock() {
        $this->UnitTestCase('Clock class test');
    }
    function testClockTellsTime() {
        $clock = new Clock();
        $this->assertEqual($clock->now(), time(), 'Now is the right time');
    }
    function testClockAdvance() {
    }
}
?>
Our only test at the moment is that our new Clock class acts as a simple PHP time() function substitute. The other method is a place holder. It's our TODO item if you like. We haven't done a test for it yet because that would spoil our rhythm. We will write the time shift functionality once we are green. At the moment we are obviously not green...

Fatal error: Failed opening required '../classes/clock.php' (include_path='') in /home/marcus/projects/lastcraft/tutorial_tests/tests/clock_test.php on line 2
We create a classes/clock.php file like so...
<?php
class Clock {
    
    function Clock() {
    }
    
    function now() {
    }
}
?>
This regains our flow ready for coding.

All tests

Fail: Clock class test->testclocktellstime->[NULL: ] should be equal to [integer: 1050257362]
3/3 test cases complete. 4 passes and 1 fails.
This is now easy to fix...
class Clock {
    
    function Clock() {
    }
    
    function now() {
        return time();
    }
}
And now we are green...

All tests

3/3 test cases complete. 5 passes and 0 fails.
There is still a problem. The clock could roll over during the assertion causing the result to be out by one second. The chances are small, but if there were a lot of timing tests you would end up with a test suite that is erratic, severely limiting its usefulness. We will
tackle this shortly and for now just jot it onto our "to do" list.

The advancement test looks like this...

class TestOfClock extends UnitTestCase {
    function TestOfClock() {
        $this->UnitTestCase('Clock class test');
    }
    function testClockTellsTime() {
        $clock = new Clock();
        $this->assertEqual($clock->now(), time(), 'Now is the right time');
    }
    function testClockAdvance() {
        $clock = new Clock();
        $clock->advance(10);
        $this->assertEqual($clock->now(), time() + 10, 'Advancement');
    }
}
The code to get to green is straight forward and just involves adding a time offset.
class Clock {
    var $_offset;
    
    function Clock() {
        $this->_offset = 0;
    }
    
    function now() {
        return time() + $this->_offset;
    }
    
    function advance($offset) {
        $this->_offset += $offset;
    }
}

Group test tidy up

Our all_tests.php file has some repetition we could do without. We have to manually add our test cases from each included file. It is possible to remove it, but use of the following requires care. The TestSuite class has a convenience method called addFile() that takes a PHP file as a parameter. This mechanism makes a note of all the classes, requires in the file and then has a look at any newly created classes. If they are descendents of TestCase they are added as a new group test.

In addition the autorun library will run all the collected test cases automagically after loading them.

Here is our refactored test suite using this method...

<?php
if (! defined('SIMPLE_TEST')) {
    define('SIMPLE_TEST', 'simpletest/');
}
require_once(SIMPLE_TEST . 'autorun.php');

class AllTests extends TestSuite {
    function AllTests() {
        $this->TestSuite('All tests');
        $this->addFile('log_test.php');
        $this->addFile('clock_test.php');
    }
}
?>
The pitfalls of this are...
  1. If the test file has already been included, no new classes will be added to this group
  2. If the test file has other classes that are related to TestCase then these will be added to the group test as well.
In our tests we have only test cases in the test files and we removed their inclusion from the all_tests.php script and so we are OK. This is the usual situation.

We should really fix the glitch with the possible clock rollover so we'll do this next.

Time is an often neglected variable in tests.
A clock class allows us to alter time.
Tidying the group test.
The previous section is grouping unit tests.
The next section is subclassing test cases.
You will need the SimpleTest unit tester for the examples.