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 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
|
<?xml version="1.0"?>
<page title="The Application Boundary" here="Boundary classes">
<long_title>
PHP unit testing tutorial - Organising unit tests and boundary
class test cases
</long_title>
<content>
<p>
You are probably thinking that we have well and truly exhausted
the <code>Log</code> class by now and that there is
really nothing more to add.
Things are never that simple with object oriented programming, though.
You think you understand a problem and then something comes a long
that challenges your perspective and leads to an even deeper appreciation.
I thought I understood the logging class and that only the first page
of the tutorial would use it.
After that I would move on to something more complicated.
No one is more surprised than me that I still haven't got to
the bottom of it.
In fact I think I have only just figured out what a logger does.
</p>
<p>
<a class="target" name="variation"><h2>Log variations</h2></a>
</p>
<p>
Supposing that we do not want to log to a file at all.
Perhaps we want to print to the screen, write the messages to a
socket or send them to the Unix(tm) <em>syslog</em> daemon for
dispatching across the network.
How do we incorporate this variation?
</p>
<p>
Simplest is to subclass the <code>Log</code>
overriding the <code>message()</code> method
with new versions.
This will work in the short term, but there is actually something
subtle, but deeply wrong with this.
Suppose we do subclass and have loggers that write to files,
the screen and the network.
Three classes , but that is OK.
Now suppose that we want a new logging class that adds message
filtering by priority, letting only certain types of messages
through according to some configuration file.
</p>
<p>
We are stuck. If we subclass again, we have to do it for all
three classes, giving us six classes.
The amount of duplication is horrible.
</p>
<p>
So are you now wishing that PHP had multiple inheritence?
Well, here that would reduce the short term workload, but
complicate what should be a very simple class.
Multiple inheritance, even when supported, should be used
extremely carefully as all sorts of complicated entanglements
can result.
Treat it as a loaded gun.
In fact, our sudden need for it is telling us something else - perhaps
that we have gone wrong on the conceptual level.
</p>
<p>
What does a logger do?
Does it send messages to a file?
Does it send messages to a network?
Does it send messages to a screen?
Nope.
It just sends messages (full stop).
The target of those messages can be chosen when setting
up the log, but after that the
logger should be left to combine and format the message
elements as that is its real job.
We restricted ourselves by assuming that target was a filename.
</p>
<p>
<a class="target" name="writer"><h2>Abstracting a file to a writer</h2></a>
</p>
<p>
The solution to this plight is a real classic.
First we encapsulate the variation in a class as this will
add a level of indirection.
Instead of passing in the file name as a string we
will pass the "thing that we will write to"
which we will call a <code>Writer</code>.
Back to the tests...
<php><![CDATA[
<?php
require_once('../classes/log.php');
require_once('../classes/clock.php');<strong>
require_once('../classes/writer.php');</strong>
Mock::generate('Clock');
class TestOfLogging extends UnitTestCase {
function TestOfLogging() {
$this->UnitTestCase('Log class test');
}
function setUp() {
@unlink('../temp/test.log');
}
function tearDown() {
@unlink('../temp/test.log');
}
function getFileLine($filename, $index) {
$messages = file($filename);
return $messages[$index];
}
function testCreatingNewFile() {<strong>
$log = new Log(new FileWriter('../temp/test.log'));</strong>
$this->assertFalse(file_exists('../temp/test.log'), 'Created before message');
$log->message('Should write this to a file');
$this->assertTrue(file_exists('../temp/test.log'), 'File created');
}
function testAppendingToFile() {<strong>
$log = new Log(new FileWriter('../temp/test.log'));</strong>
$log->message('Test line 1');
$this->assertWantedPattern(
'/Test line 1/',
$this->getFileLine('../temp/test.log', 0));
$log->message('Test line 2');
$this->assertWantedPattern(
'/Test line 2/',
$this->getFileLine('../temp/test.log', 1));
}
function testTimestamps() {
$clock = &new MockClock($this);
$clock->setReturnValue('now', 'Timestamp');<strong>
$log = new Log(new FileWriter('../temp/test.log'));</strong>
$log->message('Test line', &$clock);
$this->assertWantedPattern(
'/Timestamp/',
$this->getFileLine('../temp/test.log', 0),
'Found timestamp');
}
}
?>
]]></php>
I am going to do this one step at a time so as not to get
confused.
I have replaced the file names with an imaginary
<code>FileWriter</code> class from
a file <em>classes/writer.php</em>.
This will cause the tests to crash as we have not written
the writer yet.
Should we do that now?
</p>
<p>
We could, but we don't have to.
We do need to create the interface, though, or we won't be
able to mock it.
This makes <em>classes/writer.php</em> looks like...
<php><![CDATA[
<?php
class FileWriter {
function FileWriter($file_path) {
}
function write($message) {
}
}
?>
]]></php>
We need to modify the <code>Log</code> class
as well...
<php><![CDATA[
<?php
require_once('../classes/clock.php');<strong>
require_once('../classes/writer.php');</strong>
class Log {<strong>
var $_writer;</strong>
function Log(<strong>&$writer</strong>) {<strong>
$this->_writer = &$writer;</strong>
}
function message($message, $clock = false) {
if (! is_object($clock)) {
$clock = new Clock();
}<strong>
$this->_writer->write("[" . $clock->now() . "] $message");</strong>
}
}
?>
]]></php>
There is not much that hasn't changed in our now even smaller
class.
The tests run, but fail at this point unless we add code to
the writer.
What do we do now?
</p>
<p>
We could start writing tests and code the
<code>FileWriter</code> class alongside, but
while we were doing this our <code>Log</code>
tests would be failing and disturbing our focus.
In fact we do not have to.
</p>
<p>
Part of our plan is to free the logging class from the file
system and there is a way to do this.
First we add a <em>tests/writer_test.php</em> so that
we have somewhere to place our test code from <em>log_test.php</em>
that we are going to shuffle around.
I won't yet add it to the <em>all_tests.php</em> file
though as it is the logging aspect we are tackling right now.
</p>
<p>
Now I have done that (honest) we remove any
tests from <em>log_test.php</em> that are not strictly logging
related and move them to <em>writer_test.php</em> for later.
We will also mock the writer so that it does not write
out to real files...
<php><![CDATA[
<?php
require_once('../classes/log.php');
require_once('../classes/clock.php');
require_once('../classes/writer.php');
Mock::generate('Clock');<strong>
Mock::generate('FileWriter');</strong>
class TestOfLogging extends UnitTestCase {
function TestOfLogging() {
$this->UnitTestCase('Log class test');
}<strong>
function testWriting() {
$clock = &new MockClock();
$clock->setReturnValue('now', 'Timestamp');
$writer = &new MockFileWriter($this);
$writer->expectArguments('write', array('[Timestamp] Test line'));
$writer->expectCallCount('write', 1);
$log = &new Log(\$writer);
$log->message('Test line', &$clock);
}</strong>
}
?>
]]></php>
Yes that really is the whole test case and it really is that short.
A lot has happened here...
<ol>
<li>
The requirement to create the file only when needed has
moved to the <code>FileWriter</code>.
</li>
<li>
As we are dealing with mocks, no files are actually
created and so I moved the
<code>setUp()</code> and
<code>tearDown()</code> off into the
writing tests.
</li>
<li>
The test now consists of sending a sample message and
testing the format.
</li>
</ol>
Hang on a minute, where are the assertions?
</p>
<p>
The mock objects do much more than simply behave like other
objects, they also run tests.
The <code>expectArguments()</code>
call told the mock to expect a single parameter of
the string "[Timestamp] Test line" when
the mock <code>write()</code> method is
called.
When that method is called the expected parameters are
compared with this and either a pass or a fail is sent
to the unit test as a result.
</p>
<p>
The other expectation is that <code>write()</code>
will be called only once.
Simply setting this up is not enough.
We can see all this in action by running the tests...
<div class="demo">
<h1>All tests</h1>
<span class="pass">Pass</span>: log_test.php->Log class test->testwriting->Arguments for [write] were [String: [Timestamp] Test line]<br />
<span class="pass">Pass</span>: log_test.php->Log class test->testwriting->Expected call count for [write] was [1], but got [1]<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: green; color: white;">3/3 test cases complete.
<strong>4</strong> passes and <strong>0</strong> fails.</div>
</div>
</p>
<p>
We can actually shorten our test slightly more.
The mock object expectation <code>expectOnce()</code>
can actually combine the two seperate expectations...
<php><![CDATA[
function testWriting() {
$clock = &new MockClock();
$clock->setReturnValue('now', 'Timestamp');
$writer = &new MockFileWriter($this);<strong>
$writer->expectOnce('write', array('[Timestamp] Test line'));</strong>
$log = &new Log($writer);
$log->message('Test line', &$clock);
}
]]></php>
This can be an effective shorthand.
</p>
<p>
<a class="target" name="boundary"><h2>Boundary classes</h2></a>
</p>
<p>
Something very nice has happened to the logger besides merely
getting smaller.
</p>
<p>
The only things it depends on now are classes that we have written
ourselves and
in the tests these are mocked and so there are no dependencies
on anything other than our own PHP code.
No writing to files or waiting for clocks to tick over.
This means that the <em>log_test.php</em> test case will
run as fast as the processor will carry it.
By contrast the <code>FileWriter</code>
and <code>Clock</code> classes are very
close to the system.
This makes them harder to test as real data must be moved
around and painstakingly confirmed, often by ad hoc tricks.
</p>
<p>
Our last refactoring has helped a lot.
The hard to test classes on the boundary of the application
and the system are now smaller as the I/O code has
been further separated from the application logic.
They are direct mappings to PHP operations:
<code>FileWriter::write()</code> maps
to PHP <code>fwrite()</code> with the
file opened for appending and
<code>Clock::now()</code> maps to
PHP <code>time()</code>.
This makes debugging easier.
It also means that these classes will change less often.
</p>
<p>
If they don't change a lot then there is no reason to
keep running the tests for them.
This means that tests for the boundary classes can be moved
off into there own test suite leaving the other unit tests
to run at full speed.
In fact this is what I tend to do and the test cases
in <a href="simple_test.php">SimpleTest</a> itself are
divided this way.
</p>
<p>
That may not sound like much with one unit test and two
boundary tests, but typical applications can have
twenty boundary classes and two hundred application
classes.
To keep these running at full speed you will want
to keep them separate.
</p>
<p>
Besides, separating off decisions of which system components
to use is good development.
Perhaps all this mocking is
<a href="improving_design_tutorial.php">improving our design</a>?
</p>
</content>
<internal>
<link>
<a href="#variation">Handling variation</a> in our logger.
</link>
<link>
Abstracting further with a <a href="#writer">mock Writer</a> class.
</link>
<link>
Separating <a href="#boundary">Boundary class</a> tests
cleans things up.
</link>
</internal>
<external>
<link>
This tutorial follows the <a href="mock_objects_tutorial.php">Mock objects</a> introduction.
</link>
<link>
Next is <a href="improving_design_tutorial.php">test driven design</a>.
</link>
<link>
You will need the <a href="simple_test.php">SimpleTest testing framework</a>
to try these examples.
</link>
</external>
<meta>
<keywords>
software development,
php programming,
programming php,
software development tools,
php tutorial,
free php scripts,
organizing unit tests,
testing tips,
development tricks,
software architecture for testing,
php example code,
mock objects,
junit port,
test case examples,
php testing,
unit test tool,
php test suite
</keywords>
</meta>
</page>
|