You've already forked postfixadmin
mirror of
https://github.com/postfixadmin/postfixadmin.git
synced 2026-01-14 12:02:20 +03:00
git-svn-id: https://svn.code.sf.net/p/postfixadmin/code/trunk@581 a1433add-5e2c-0410-b055-b7f2511e0802
299 lines
11 KiB
XML
299 lines
11 KiB
XML
<?xml version="1.0"?>
|
|
<page title="Taking control of testing" here="Taking control">
|
|
<long_title>PHP unit testing tutorial - Isolating variables when testing</long_title>
|
|
<content>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<p>
|
|
<a class="target" name="time"><h2>Controlling time</h2></a>
|
|
<p>
|
|
</p>
|
|
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.
|
|
</p>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<p>
|
|
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 <code>sleep(3)</code>
|
|
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.
|
|
</p>
|
|
<p>
|
|
<a class="target" name="clock"><h2>A clock class</h2></a>
|
|
Again we will design our clock wrapper by writing tests.
|
|
Firstly we add a clock test case to our <em>tests/all_tests.php</em>
|
|
test suite...
|
|
<php><![CDATA[
|
|
<?php
|
|
if (! defined('SIMPLE_TEST')) {
|
|
define('SIMPLE_TEST', 'simpletest/');
|
|
}
|
|
require_once(SIMPLE_TEST . 'unit_tester.php');
|
|
require_once(SIMPLE_TEST . 'reporter.php');
|
|
require_once('log_test.php');<strong>
|
|
require_once('clock_test.php');</strong>
|
|
|
|
$test = &new TestSuite('All tests');
|
|
$test->addTestCase(new TestOfLogging());<strong>
|
|
$test->addTestCase(new TestOfClock());</strong>
|
|
$test->run(new HtmlReporter());
|
|
?>
|
|
]]></php>
|
|
Then we create the test case in the new file
|
|
<em>tests/clock_test.php</em>...
|
|
<php><![CDATA[
|
|
<strong><?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() {
|
|
}
|
|
}
|
|
?></strong>
|
|
]]></php>
|
|
Our only test at the moment is that our new
|
|
<code>Clock</code> class acts
|
|
as a simple PHP <code>time()</code>
|
|
function substitute.
|
|
The other method is a place holder.
|
|
It's our <em>TODO</em> 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...
|
|
<div class="demo">
|
|
<br />
|
|
<b>Fatal error</b>: Failed opening required '../classes/clock.php' (include_path='') in
|
|
<b>/home/marcus/projects/lastcraft/tutorial_tests/tests/clock_test.php</b> on line <b>2</b>
|
|
<br />
|
|
</div>
|
|
We create a <em>classes/clock.php</em> file like so...
|
|
<php><![CDATA[
|
|
<strong><?php
|
|
class Clock {
|
|
|
|
function Clock() {
|
|
}
|
|
|
|
function now() {
|
|
}
|
|
}
|
|
?></strong>
|
|
]]></php>
|
|
This regains our flow ready for coding.
|
|
<div class="demo">
|
|
<h1>All tests</h1>
|
|
<span class="fail">Fail</span>: Clock class test->testclocktellstime->[NULL: ] should be equal to [integer: 1050257362]<br />
|
|
<div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">3/3 test cases complete.
|
|
<strong>4</strong> passes and <strong>1</strong> fails.</div>
|
|
</div>
|
|
This is now easy to fix...
|
|
<php><![CDATA[
|
|
class Clock {
|
|
|
|
function Clock() {
|
|
}
|
|
|
|
function now() {<strong>
|
|
return time();</strong>
|
|
}
|
|
}
|
|
]]></php>
|
|
And now we are green...
|
|
<div class="demo">
|
|
<h1>All tests</h1>
|
|
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">3/3 test cases complete.
|
|
<strong>5</strong> passes and <strong>0</strong> fails.</div>
|
|
</div>
|
|
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 <a href="subclass_tutorial.php">tackle this shortly</a> and for now just jot it onto our
|
|
"to do" list.
|
|
</p>
|
|
<p>
|
|
The advancement test looks like this...
|
|
<php><![CDATA[
|
|
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');
|
|
}<strong>
|
|
function testClockAdvance() {
|
|
$clock = new Clock();
|
|
$clock->advance(10);
|
|
$this->assertEqual($clock->now(), time() + 10, 'Advancement');
|
|
}</strong>
|
|
}
|
|
]]></php>
|
|
The code to get to green is straight forward and just involves adding
|
|
a time offset.
|
|
<php><![CDATA[
|
|
class Clock {<strong>
|
|
var $_offset;</strong>
|
|
|
|
function Clock() {<strong>
|
|
$this->_offset = 0;</strong>
|
|
}
|
|
|
|
function now() {
|
|
return time()<strong> + $this->_offset</strong>;
|
|
}
|
|
<strong>
|
|
function advance($offset) {
|
|
$this->_offset += $offset;
|
|
}</strong>
|
|
}
|
|
]]></php>
|
|
</p>
|
|
<p>
|
|
<a class="target" name="tidy"><h2>Group test tidy up</h2></a>
|
|
Our <em>all_tests.php</em> 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 <code>TestSuite</code> class has a
|
|
convenience method called <code>addTestFile()</code>
|
|
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 <code>TestCase</code>
|
|
they are added as a new group test.
|
|
</p>
|
|
<p>
|
|
Here is our refactored test suite using this method...
|
|
<php><![CDATA[
|
|
<?php
|
|
if (! defined('SIMPLE_TEST')) {
|
|
define('SIMPLE_TEST', 'simpletest/');
|
|
}<strong>
|
|
require_once(SIMPLE_TEST . 'unit_tester.php');
|
|
require_once(SIMPLE_TEST . 'reporter.php');</strong>
|
|
|
|
$test = &new TestSuite('All tests');<strong>
|
|
$test->addTestFile('log_test.php');
|
|
$test->addTestFile('clock_test.php');</strong>
|
|
$test->run(new HtmlReporter());
|
|
?>
|
|
]]></php>
|
|
The pitfalls of this are...
|
|
<ol>
|
|
<li>
|
|
If the test file has already been included,
|
|
no new classes will be added to this group
|
|
</li>
|
|
<li>
|
|
If the test file has other classes that are
|
|
related to <code>TestCase</code>
|
|
then these will be added to the group test as well.
|
|
</li>
|
|
</ol>
|
|
In our tests we have only test cases in the test files and
|
|
we removed their inclusion from the <em>all_tests.php</em>
|
|
script and so we are OK.
|
|
This is the usual situation.
|
|
</p>
|
|
<p>
|
|
We should really fix the glitch with the possible
|
|
clock rollover so we'll
|
|
<a href="subclass_tutorial.php">do this next</a>.
|
|
</p>
|
|
</content>
|
|
<internal>
|
|
<link><a href="#time">Time</a> is an often neglected variable in tests.</link>
|
|
<link>A <a href="#clock">clock class</a> allows us to alter time.</link>
|
|
<link><a href="#tidy">Tidying the group test</a>.</link>
|
|
</internal>
|
|
<external>
|
|
<link>
|
|
The previous section is
|
|
<a href="group_test_tutorial.php">grouping unit tests</a>.
|
|
</link>
|
|
<link>
|
|
The next section is
|
|
<a href="subclass_tutorial.php">subclassing test cases</a>.
|
|
</link>
|
|
<link>
|
|
You will need
|
|
<a href="simple_test.php">the SimpleTest unit tester</a>
|
|
for the examples.
|
|
</link>
|
|
</external>
|
|
<meta>
|
|
<keywords>
|
|
software development,
|
|
php programming,
|
|
programming php,
|
|
software development tools,
|
|
php tutorial,
|
|
free php scripts,
|
|
architecture,
|
|
php resources,
|
|
mock objects,
|
|
junit,
|
|
php testing,
|
|
unit test,
|
|
php testing
|
|
</keywords>
|
|
</meta>
|
|
</page> |