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 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
|
<?xml version="1.0"?>
<page title="Mock Objects" here="Using mock objects">
<long_title>PHP unit testing tutorial - Using mock objects in PHP</long_title>
<content>
<p>
<a class="target" name="refactor"><h2>Refactoring the tests again</h2></a>
</p>
<p>
Before more functionality is added there is some refactoring
to do.
We are going to do some timing tests and so the
<code>TimeTestCase</code> class definitely needs
its own file.
Let's say <em>tests/time_test_case.php</em>...
<php><![CDATA[
<strong><?php
if (! defined('SIMPLE_TEST')) {
define('SIMPLE_TEST', 'simpletest/');
}
require_once(SIMPLE_TEST . 'unit_tester.php');
class TimeTestCase extends UnitTestCase {
function TimeTestCase($test_name = '') {
$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);
}
}
?></strong>
]]></php>
We can then <code>require()</code> this file into
the <em>all_tests.php</em> script.
</p>
<p>
<a class="target" name="timestamp"><h2>Adding a timestamp to the Log</h2></a>
</p>
<p>
I don't know quite what the format of the log message should
be for the test, so to check for a timestamp we could do the
simplest possible thing, which is to look for a sequence of digits.
<php><![CDATA[
<?php
require_once('../classes/log.php');<strong>
require_once('../classes/clock.php');
class TestOfLogging extends TimeTestCase {
function TestOfLogging() {
$this->TimeTestCase('Log class test');
}</strong>
function setUp() {
@unlink('../temp/test.log');
}
function tearDown() {
@unlink('../temp/test.log');
}
function getFileLine($filename, $index) {
$messages = file($filename);
return $messages[$index];
}
function testCreatingNewFile() {
...
}
function testAppendingToFile() {
...
}<strong>
function testTimestamps() {
$log = new Log('../temp/test.log');
$log->message('Test line');
$this->assertTrue(
preg_match('/(\d+)/', $this->getFileLine('../temp/test.log', 0), $matches),
'Found timestamp');
$clock = new clock();
$this->assertSameTime((integer)$matches[1], $clock->now(), 'Correct time');
}</strong>
}
?>
]]></php>
The test case creates a new <code>Log</code>
object and writes a message.
We look for a digit sequence and then test it against the current
time using our <code>Clock</code> object.
Of course it doesn't work until we write the code.
<div class="demo">
<h1>All tests</h1>
<span class="pass">Pass</span>: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 1/] in [Test line 1]<br />
<span class="pass">Pass</span>: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 2/] in [Test line 2]<br />
<span class="pass">Pass</span>: log_test.php->Log class test->testcreatingnewfile->Created before message<br />
<span class="pass">Pass</span>: log_test.php->Log class test->testcreatingnewfile->File created<br />
<span class="fail">Fail</span>: log_test.php->Log class test->testtimestamps->Found timestamp<br />
<br />
<b>Notice</b>: Undefined offset: 1 in <b>/home/marcus/projects/lastcraft/tutorial_tests/tests/log_test.php</b> on line <b>44</b><br />
<span class="fail">Fail</span>: log_test.php->Log class test->testtimestamps->Correct time<br />
<span class="pass">Pass</span>: clock_test.php->Clock class test->testclockadvance->Advancement<br />
<span class="pass">Pass</span>: clock_test.php->Clock class test->testclocktellstime->Now is the right time<br />
<div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">3/3 test cases complete.
<strong>6</strong> passes and <strong>2</strong> fails.</div>
</div>
The test suite is still showing the passes from our earlier
modification.
</p>
<p>
We can get the tests to pass simply by adding a timestamp
when writing out to the file.
Yes, of course all of this is trivial and
I would not normally test this fanatically, but it is going
to illustrate a more general problem.
The <em>log.php</em> file becomes...
<php><![CDATA[
<?php<strong>
require_once('../classes/clock.php');</strong>
class Log {
var $_file_path;
function Log($file_path) {
$this->_file_path = $file_path;
}
function message($message) {<strong>
$clock = new Clock();</strong>
$file = fopen($this->_file_path, 'a');<strong>
fwrite($file, "[" . $clock->now() . "] $message\n");</strong>
fclose($file);
}
}
?>
]]></php>
The tests should now pass.
</p>
<p>
Our new test is full of problems, though.
What if our time format changes to something else?
Things are going to be a lot more complicated to test if this
happens.
It also means that any changes to the clock class time
format will cause our logging tests to fail also.
This means that our log tests are tangled up with the clock tests
and extremely fragile.
It lacks cohesion, which is the same as saying it is not
tightly focused, testing facets of the clock as well as the log.
Our problems are caused in part because the clock output
is unpredictable when
all we really want to test is that the logging message
contains the output of
<code>Clock::now()</code>.
We don't
really care about the contents of that method call.
</p>
<p>
Can we make that call predictable?
We could if we could get the log to use a dummy version
of the clock for the duration of the test.
The dummy clock class would have to behave the same way
as the <code>Clock</code> class
except for the fixed output from the
<code>now()</code> method.
Hey, that would even free us from using the
<code>TimeTestCase</code> class!
</p>
<p>
We could write such a class pretty easily although it is
rather tedious work.
We just create another clock class with same interface
except that the <code>now()</code> method
returns a value that we can change with some other setter method.
That is quite a lot of work for a pretty minor test.
</p>
<p>
Except that it is really no work at all.
</p>
<p>
<a class="target" name="mock"><h2>A mock clock</h2></a>
</p>
<p>
To reach instant testing clock nirvana we need
only three extra lines of code...
<php><![CDATA[
require_once('simpletest/mock_objects.php');
]]></php>
This includes the mock generator code.
It is simplest to place this in the <em>all_tests.php</em>
script as it gets used rather a lot.
<php><![CDATA[
Mock::generate('Clock');
]]></php>
This is the line that does the work.
The code generator scans the class for all of its
methods, creates code to generate an identically
interfaced class, but with the name "Mock" added,
and then <code>eval()</code>s the new code to
create the new class.
<php><![CDATA[
$clock = &new MockClock($this);
]]></php>
This line can be added to any test method we are interested in.
It creates the dummy clock ready to receive our instructions.
</p>
<p>
Our test case is on the first steps of a radical clean up...
<php><![CDATA[
<?php
require_once('../classes/log.php');
require_once('../classes/clock.php');<strong>
Mock::generate('Clock');
class TestOfLogging extends UnitTestCase {
function TestOfLogging() {
$this->UnitTestCase('Log class test');
}</strong>
function setUp() {
@unlink('../temp/test.log');
}
function tearDown() {
@unlink('../temp/test.log');
}
function getFileLine($filename, $index) {
$messages = file($filename);
return $messages[$index];
}
function testCreatingNewFile() {
...
}
function testAppendingToFile() {
...
}
function testTimestamps() {<strong>
$clock = &new MockClock($this);
$clock->setReturnValue('now', 'Timestamp');
$log = new Log('../temp/test.log');
$log->message('Test line', &$clock);
$this->assertWantedPattern(
'/Timestamp/',
$this->getFileLine('../temp/test.log', 0),
'Found timestamp');</strong>
}
}
?>
]]></php>
This test method creates a <code>MockClock</code>
object and then sets the return value of the
<code>now()</code> method to be the string
"Timestamp".
Every time we call <code>$clock->now()</code>
it will return this string.
This should be easy to spot.
</p>
<p>
Next we create our log and send a message.
We pass into the <code>message()</code>
call the clock we would like to use.
This means that we will have to add an optional parameter to
the logging class to make testing possible...
<php><![CDATA[
class Log {
var $_file_path;
function Log($file_path) {
$this->_file_path = $file_path;
}
function message($message, <strong>$clock = false</strong>) {<strong>
if (!is_object($clock)) {
$clock = new Clock();
}</strong>
$file = fopen($this->_file_path, 'a');
fwrite($file, "[" . $clock->now() . "] $message\n");
fclose($file);
}
}
]]></php>
All of the tests now pass and they test only the logging code.
We can breathe easy again.
</p>
<p>
Does that extra parameter in the <code>Log</code>
class bother you?
We have changed the interface just to facilitate testing after
all.
Are not interfaces the most important thing?
Have we sullied our class with test code?
</p>
<p>
Possibly, but consider this.
Next chance you get, look at a circuit board, perhaps the motherboard
of the computer you are looking at right now.
On most boards you will find the odd empty hole, or solder
joint with nothing attached or perhaps a pin or socket
that has no obvious function.
Chances are that some of these are for expansion and
variations, but most of the remainder will be for testing.
</p>
<p>
Think about that.
The factories making the boards many times over wasting material
on parts that do not add to the final function.
If hardware engineers can make this sacrifice of elegance I am
sure we can too.
Our sacrifice wastes no materials after all.
</p>
<p>
Still bother you?
Actually it bothers me too, but not so much here.
The number one priority is code that works, not prizes
for minimalism.
If it really bothers you, then move the creation of the clock
into another protected factory method.
Then subclass the clock for testing and override the
factory method with one that returns the mock.
Your tests are clumsier, but your interface is intact.
</p>
<p>
Again I leave the decision to you.
</p>
</content>
<internal>
<link>
<a href="#refactor">Refactoring the tests</a> so we can reuse
our new time test.
</link>
<link>Adding <a href="#timestamp">Log timestamps</a>.</link>
<link><a href="#mock">Mocking the clock</a> to make the test cohesive.</link>
</internal>
<external>
<link>
This follows the <a href="first_test_tutorial.php">unit test tutorial</a>.
</link>
<link>
Next is distilling <a href="boundary_classes_tutorial.php">boundary classes</a>.
</link>
<link>
You will need the <a href="simple_test.php">SimpleTest</a>
tool to run the examples.
</link>
<link>
<a href="http://www.mockobjects.com/">Mock objects</a> papers.
</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>
|