1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
|
<?xml version="1.0"?>
<page title="Subclassing a unit test case" here="Reusing cases">
<long_title>PHP unit testing tutorial - Subclassing a test case</long_title>
<content>
<p>
<a class="target" name="time"><h2>A timing insensitive assertion</h2></a>
</p>
<p>
We left our clock test with a hole.
If the PHP <code>time()</code>
function rolled over during this comparison...
<php><![CDATA[
function testClockTellsTime() {
$clock = new Clock();
$this->assertEqual($clock->now(), time(), 'Now is the right time');
}
]]></php>
...our test would be out by one second and would cause
a false failure.
Erratic behaviour of our test suite is not what we want when
we could be running it a hundred times a day.
</p>
<p>
We could rewrite the test as...
<php><![CDATA[
function testClockTellsTime() {
$clock = new Clock();<strong>
$time1 = $clock->now();
$time2 = time();
$this->assertTrue($time1 == $time2) || ($time1 + 1 == $time2), 'Now is the right time');</strong>
}
]]></php>
This is hardly a clear design though and we will have to repeat
this for every timing test that we do.
Repetition is public enemy number one and so we'll
use this as incentive to factor out the new test code.
<php><![CDATA[
class TestOfClock extends UnitTestCase {
function TestOfClock() {
$this->UnitTestCase('Clock class test');
}<strong>
function assertSameTime($time1, $time2, $message) {
$this->assertTrue(
($time1 == $time2) || ($time1 + 1 == $time2),
$message);
}</strong>
function testClockTellsTime() {
$clock = new Clock();<strong>
$this->assertSameTime($clock->now(), time(), 'Now is the right time');</strong>
}
function testClockAdvance() {
$clock = new Clock();
$clock->advance(10);<strong>
$this->assertSameTime($clock->now(), time() + 10, 'Advancement');</strong>
}
}
]]></php>
Of course each time I make one of these changes I rerun the
tests to make sure we are still OK.
Refactor on green.
It's a lot safer.
</p>
<p>
<a class="target" name="subclass"><h2>Reusing our assertion</h2></a>
</p>
<p>
It may be that we want more than one test case that is
timing sensitive.
Perhaps we are reading timestamps from database rows
or other places that could allow an extra second to
tick over.
For these new test classes to take advantage of our new assertion
we need to place it into a superclass.
</p>
<p>
Here is the complete <em>clock_test.php</em> file after
promoting our <code>assertSameTime()</code>
method to its own superclass...
<php><![CDATA[
<?php
require_once('../classes/clock.php');
<strong>
class TimeTestCase extends UnitTestCase {
function TimeTestCase($test_name) {
$this->UnitTestCase($test_name);
}
function assertSameTime($time1, $time2, $message) {
$this->assertTrue(
($time1 == $time2) || ($time1 + 1 == $time2),
$message);
}
}
class TestOfClock extends TimeTestCase {
function TestOfClock() {
$this->TimeTestCase('Clock class test');
}</strong>
function testClockTellsTime() {
$clock = new Clock();
$this->assertSameTime($clock->now(), time(), 'Now is the right time');
}
function testClockAdvance() {
$clock = new Clock();
$clock->advance(10);
$this->assertSameTime($clock->now(), time() + 10, 'Advancement');
}<strong>
}</strong>
?>
]]></php>
Now we get the benefit of our new assertion every
time we inherit from our own
<code>TimeTestCase</code> class
rather than the default
<code>UnitTestCase</code>.
This is very much how the JUnit tool was designed
to be used and SimpleTest is a port of that interface.
It is a testing framework from which your own test
system can be grown.
</p>
<p>
If we run the tests now we get a slight niggle...
<div class="demo">
<b>Warning</b>: Missing argument 1 for timetestcase()
in <b>/home/marcus/projects/lastcraft/tutorial_tests/tests/clock_test.php</b> on line <b>5</b><br />
<h1>All tests</h1>
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">3/3 test cases complete.
<strong>6</strong> passes and <strong>0</strong> fails.</div>
</div>
The reasons for this are quite tricky.
</p>
<p>
Our subclass requires a constructor parameter that has
not been supplied and yet it appears that we did
supply it.
When we inherited our new class we passed it in our own
constructor.
It's right here...
<php><![CDATA[
function TestOfClock() {
$this->TimeTestCase('Clock class test');
}
]]></php>
In fact we are right, that is not the problem.
</p>
<p>
Remember when we built our <em>all_tests.php</em>
group test by using the
<code>addTestFile()</code> method.
This method looks for test case classes, instantiates
them if they are new and then runs all of their tests.
What's happened is that it has found our
test case extension as well.
This is harmless as there are no test methods within it,
that is, method names that start with the string
"test".
No extra tests are run.
</p>
<p>
The trouble is that it instantiates the class and does this without
the <code>$test_name</code> parameter
which is what causes our warning.
This parameter is not normally required of a test
case and not normally of its assertions either.
To make our extended test case match the
<code>UnitTestCase</code> interface
we must make these optional...
<php><![CDATA[
class TimeTestCase extends UnitTestCase {
function TimeTestCase(<strong>$test_name = false</strong>) {
$this->UnitTestCase($test_name);
}
function assertSameTime($time1, $time2, <strong>$message = false</strong>) {<strong>
if (! $message) {
$message = "Time [$time1] should match time [$time2]";
}</strong>
$this->assertTrue(
($time1 == $time2) || ($time1 + 1 == $time2),
$message);
}
}
]]></php>
Of course it should still bother you that this class is
instantiated by the test suite unnecessarily.
Here is a modification to prevent it running...
<php><![CDATA[
<strong>SimpleTestOptions::ignore('TimeTestCase');</strong>
class TimeTestCase extends UnitTestCase {
function TimeTestCase($test_name = false) {
$this->UnitTestCase($test_name);
}
function assertSameTime($time1, $time2, $message = '') {
if (!$message) {
$message = "Time [$time1] should match time [$time2]";
}
$this->assertTrue(
($time1 == $time2) || ($time1 + 1 == $time2),
$message);
}
}
]]></php>
This just tells SimpleTest to always ignore this class when
building test suites.
It can be included anywhere in the test case file.
</p>
<p>
Six passes looks good, but does not tell the casual
observer what has been tested.
For that you have to look at the code.
If that sounds like drudge to you and you would like this
information displayed before you then we should go on
to <a local="display_subclass_tutorial">show the passes</a> next.
</p>
</content>
<internal>
<link>
A <a href="#time">timing insensitive assertion</a>
that allows a one second gain.
</link>
<link>
<a href="#subclass">Subclassing the test case</a>
so as to reuse the test method.
</link>
</internal>
<external>
<link>
The previous section was
<a local="gain_control_tutorial">controlling test variables</a>.
</link>
<link>
The next tutorial section was
<a local="display_subclass_tutorial">changing the test display</a>.
</link>
<link>
You will need the
<a local="simple_test">SimpleTest test tool</a> to run the
sample code.
</link>
</external>
<meta>
<keywords>
software development,
test case example,
programming php,
software development tools,
php tutorial,
creating subclass,
free php scripts,
architecture,
php resources,
junit,
phpunit style testing,
unit test,
php testing
</keywords>
</meta>
</page>
|