Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<html>
2
<head>
3
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
4
<title>
5
        Download the Simple Test testing framework -
6
        Unit tests and mock objects for PHP
7
    </title>
8
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
9
</head>
10
<body>
11
<div class="menu_back">
12
<div class="menu">
13
<h2>
14
<span class="chosen">SimpleTest</span>
15
</h2>
16
<ul>
17
<li>
18
<a href="overview.html">Overview</a>
19
</li>
20
<li>
21
<a href="unit_test_documentation.html">Unit tester</a>
22
</li>
23
<li>
24
<a href="group_test_documentation.html">Group tests</a>
25
</li>
26
<li>
27
<a href="mock_objects_documentation.html">Mock objects</a>
28
</li>
29
<li>
30
<a href="partial_mocks_documentation.html">Partial mocks</a>
31
</li>
32
<li>
33
<a href="reporter_documentation.html">Reporting</a>
34
</li>
35
<li>
36
<a href="expectation_documentation.html">Expectations</a>
37
</li>
38
<li>
39
<a href="web_tester_documentation.html">Web tester</a>
40
</li>
41
<li>
42
<a href="form_testing_documentation.html">Testing forms</a>
43
</li>
44
<li>
45
<a href="authentication_documentation.html">Authentication</a>
46
</li>
47
<li>
48
<a href="browser_documentation.html">Scriptable browser</a>
49
</li>
50
</ul>
51
</div>
52
</div>
53
<h1>Simple Test for PHP</h1>
54
<div class="content">
55
 
56
 
57
            <p>
58
                The following assumes that you are familiar with the concept
59
                of unit testing as well as the PHP web development language.
60
                It is a guide for the impatient new user of
61
                <a href="https://sourceforge.net/project/showfiles.php?group_id=76550">SimpleTest</a>.
62
                For fuller documentation, especially if you are new
63
                to unit testing see the ongoing
64
                <a href="unit_test_documentation.html">documentation</a>, and for
65
                example test cases see the
66
                <a href="http://www.lastcraft.com/first_test_tutorial.php">unit testing tutorial</a>.
67
            </p>
68
 
69
        <p>
70
<a class="target" name="unit">
71
<h2>Using the tester quickly</h2>
72
</a>
73
</p>
74
            <p>
75
                Amongst software testing tools, a unit tester is the one
76
                closest to the developer.
77
                In the context of agile development the test code sits right
78
                next to the source code as both are written simultaneously.
79
                In this context SimpleTest aims to be a complete PHP developer
80
                test solution and is called "Simple" because it
81
                should be easy to use and extend.
82
                It wasn't a good choice of name really.
83
                It includes all of the typical functions you would expect from
84
                <a href="http://www.junit.org/">JUnit</a> and the
85
                <a href="http://sourceforge.net/projects/phpunit/">PHPUnit</a>
86
                ports, but also adds
87
                <a href="http://www.mockobjects.com">mock objects</a>.
88
                It has some <a href="http://sourceforge.net/projects/jwebunit/">JWebUnit</a>
89
                functionality as well.
90
                This includes web page navigation, cookie testing and form submission.
91
            </p>
92
            <p>
93
                The quickest way to demonstrate is with an example.
94
            </p>
95
            <p>
96
                Let us suppose we are testing a simple file logging class called
97
                <span class="new_code">Log</span> in <em>classes/log.php</em>.
98
                We start by creating a test script which we will call
99
                <em>tests/log_test.php</em> and populate it as follows...
100
<pre>
101
&lt;?php<strong>
102
require_once('simpletest/unit_tester.php');
103
require_once('simpletest/reporter.php');
104
require_once('../classes/log.php');
105
 
106
class TestOfLogging extends UnitTestCase {
107
}</strong>
108
?&gt;
109
</pre>
110
                Here the <em>simpletest</em> folder is either local or in the path.
111
                You would have to edit these locations depending on where you
112
                placed the toolset.
113
                The <span class="new_code">TestOfLogging</span> is our frst test case and it's
114
                currently empty.
115
            </p>
116
            <p>
117
                Now we have five lines of scaffolding code and still no tests.
118
                However from this part on we get return on our investment very quickly.
119
                We'll assume that the <span class="new_code">Log</span> class
120
                takes the file name to write to in the constructor and we have
121
                a temporary folder in which to place this file...
122
<pre>
123
&lt;?php
124
require_once('simpletest/unit_tester.php');
125
require_once('simpletest/reporter.php');
126
require_once('../classes/log.php');
127
 
128
class TestOfLogging extends UnitTestCase {
129
    <strong>
130
    function testCreatingNewFile() {
131
        @unlink('/temp/test.log');
132
        $log = new Log('/temp/test.log');
133
        $this-&gt;assertFalse(file_exists('/temp/test.log'));
134
        $log-&gt;message('Should write this to a file');
135
        $this-&gt;assertTrue(file_exists('/temp/test.log'));
136
    }</strong>
137
}
138
?&gt;
139
</pre>
140
                When a test case runs it will search for any method that
141
                starts with the string <span class="new_code">test</span>
142
                and execute that method.
143
                We would normally have more than one test method of course.
144
                Assertions within the test methods trigger messages to the
145
                test framework which displays the result immediately.
146
                This immediate response is important, not just in the event
147
                of the code causing a crash, but also so that
148
                <span class="new_code">print</span> statements can display
149
                their content right next to the test case concerned.
150
            </p>
151
            <p>
152
                To see these results we have to actually run the tests.
153
                If this is the only test case we wish to run we can achieve
154
                it with...
155
<pre>
156
&lt;?php
157
require_once('simpletest/unit_tester.php');
158
require_once('simpletest/reporter.php');
159
require_once('../classes/log.php');
160
 
161
class TestOfLogging extends UnitTestCase {
162
 
163
    function testCreatingNewFile() {
164
        @unlink('/temp/test.log');
165
        $log = new Log('/temp/test.log');
166
        $this-&gt;assertFalse(file_exists('/temp/test.log'));
167
        $log-&gt;message('Should write this to a file');
168
        $this-&gt;assertTrue(file_exists('/temp/test.log'));
169
    }
170
}
171
<strong>
172
$test = &amp;new TestOfLogging();
173
$test-&gt;run(new HtmlReporter());</strong>
174
?&gt;
175
</pre>
176
            </p>
177
            <p>
178
                On failure the display looks like this...
179
                <div class="demo">
180
                    <h1>testoflogging</h1>
181
                    <span class="fail">Fail</span>: testcreatingnewfile-&gt;True assertion failed.<br>
182
                    <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
183
                    <strong>1</strong> passes and <strong>1</strong> fails.</div>
184
                </div>
185
                ...and if it passes like this...
186
                <div class="demo">
187
                    <h1>testoflogging</h1>
188
                    <div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
189
                    <strong>2</strong> passes and <strong>0</strong> fails.</div>
190
                </div>
191
				And if you get this...
192
                <div class="demo">
193
                    <b>Fatal error</b>:  Failed opening required '../classes/log.php' (include_path='') in <b>/home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php</b> on line <b>7</b>
194
                </div>
195
				it means you're missing the <em>classes/Log.php</em> file that could look like...
196
<pre>
197
&lt;?php
198
class Log {
199
 
200
        function Log($file_path) {
201
        }
202
 
203
		function message() {
204
		}
205
}
206
?&gt;;
207
</pre>
208
            </p>
209
 
210
        <p>
211
<a class="target" name="group">
212
<h2>Building group tests</h2>
213
</a>
214
</p>
215
            <p>
216
                It is unlikely in a real application that we will only ever run
217
                one test case.
218
                This means that we need a way of grouping cases into a test
219
                script that can, if need be, run every test in the application.
220
            </p>
221
            <p>
222
                Our first step is to strip the includes and to undo our
223
                previous hack...
224
<pre>
225
&lt;?php<strong>
226
require_once('../classes/log.php');</strong>
227
 
228
class TestOfLogging extends UnitTestCase {
229
 
230
    function testCreatingNewFile() {
231
        @unlink('/temp/test.log');
232
        $log = new Log('/temp/test.log');
233
        $this-&gt;assertFalse(file_exists('/temp/test.log'));
234
        $log-&gt;message('Should write this to a file');
235
        $this-&gt;assertTrue(file_exists('/temp/test.log'));<strong>
236
    }
237
}
238
?&gt;</strong>
239
</pre>
240
                Next we create a new file called <em>tests/all_tests.php</em>
241
                and insert the following code...
242
<pre>
243
<strong>&lt;?php
244
require_once('simpletest/unit_tester.php');
245
require_once('simpletest/reporter.php');
246
 
247
$test = &amp;new GroupTest('All tests');
248
$test-&gt;addTestFile('log_test.php');
249
$test-&gt;run(new HtmlReporter());
250
?&gt;</strong>
251
</pre>
252
                The method <span class="new_code">GroupTest::addTestFile()</span>
253
                will include the test case file and read any new classes created
254
                that are descended from <span class="new_code">SimpleTestCase</span>, of which
255
                <span class="new_code">UnitTestCase</span> is one example.
256
                Just the class names are stored for now, so that the test runner
257
                can instantiate the class when it works its way
258
                through your test suite.
259
            </p>
260
            <p>
261
                For this to work properly the test case file should not blindly include
262
                any other test case extensions that do not actually run tests.
263
                This could result in extra test cases being counted during the test
264
                run.
265
                Hardly a major problem, but to avoid this inconvenience simply add
266
                a <span class="new_code">SimpleTestOptions::ignore()</span> directive
267
                somewhere in the test case file.
268
                Also the test case file should not have been included
269
                elsewhere or no cases will be added to this group test.
270
                This would be a more serious error as if the test case classes are
271
                already loaded by PHP the <span class="new_code">GroupTest::addTestFile()</span>
272
                method will not detect them.
273
            </p>
274
            <p>
275
                To display the results it is necessary only to invoke
276
                <em>tests/all_tests.php</em> from the web server.
277
            </p>
278
 
279
        <p>
280
<a class="target" name="mock">
281
<h2>Using mock objects</h2>
282
</a>
283
</p>
284
            <p>
285
                Let's move further into the future.
286
            </p>
287
            <p>
288
                Assume that our logging class is tested and completed.
289
                Assume also that we are testing another class that is
290
                required to write log messages, say a
291
                <span class="new_code">SessionPool</span>.
292
                We want to test a method that will probably end up looking
293
                like this...
294
<pre>
295
<strong>
296
class SessionPool {
297
    ...
298
    function logIn($username) {
299
        ...
300
        $this-&gt;_log-&gt;message("User $username logged in.");
301
        ...
302
    }
303
    ...
304
}
305
</strong>
306
</pre>
307
                In the spirit of reuse we are using our
308
                <span class="new_code">Log</span> class.
309
                A conventional test case might look like this...
310
<pre>
311
<strong>
312
&lt;?php
313
require_once('../classes/log.php');
314
require_once('../classes/session_pool.php');
315
 
316
class TestOfSessionLogging extends UnitTestCase {
317
 
318
    function setUp() {
319
        @unlink('/temp/test.log');
320
    }
321
 
322
    function tearDown() {
323
        @unlink('/temp/test.log');
324
    }
325
 
326
    function testLogInIsLogged() {
327
        $log = new Log('/temp/test.log');
328
        $session_pool = &amp;new SessionPool($log);
329
        $session_pool-&gt;logIn('fred');
330
        $messages = file('/temp/test.log');
331
        $this-&gt;assertEqual($messages[0], "User fred logged in.\n");
332
    }
333
}
334
?&gt;</strong>
335
</pre>
336
                This test case design is not all bad, but it could be improved.
337
                We are spending time fiddling with log files which are
338
                not part of our test. Worse, we have created close ties
339
                with the <span class="new_code">Log</span> class and
340
                this test.
341
                What if we don't use files any more, but use ths
342
                <em>syslog</em> library instead?
343
                Did you notice the extra carriage return in the message?
344
                Was that added by the logger?
345
                What if it also added a time stamp or other data?
346
            </p>
347
            <p>
348
                The only part that we really want to test is that a particular
349
                message was sent to the logger.
350
                We reduce coupling if we can pass in a fake logging class
351
                that simply records the message calls for testing, but
352
                takes no action.
353
                It would have to look exactly like our original though.
354
            </p>
355
            <p>
356
                If the fake object doesn't write to a file then we save on deleting
357
                the file before and after each test. We could save even more
358
                test code if the fake object would kindly run the assertion for us.
359
            <p>
360
            </p>
361
                Too good to be true?
362
                Luckily we can create such an object easily...
363
<pre>
364
&lt;?php
365
require_once('../classes/log.php');
366
require_once('../classes/session_pool.php');<strong>
367
Mock::generate('Log');</strong>
368
 
369
class TestOfSessionLogging extends UnitTestCase {
370
 
371
    function testLogInIsLogged() {<strong>
372
        $log = &amp;new MockLog();
373
        $log-&gt;expectOnce('message', array('User fred logged in.'));</strong>
374
        $session_pool = &amp;new SessionPool($log);
375
        $session_pool-&gt;logIn('fred');
376
    }
377
}
378
?&gt;
379
</pre>
380
                The test will be triggered when the call to
381
                <span class="new_code">message()</span> is invoked on the
382
                <span class="new_code">MockLog</span> object.
383
                The mock call will trigger a parameter comparison and then send the
384
                resulting pass or fail event to the test display.
385
                Wildcards can be included here too so as to prevent tests
386
                becoming too specific.
387
            </p>
388
            <p>
389
                If the mock reaches the end of the test case without the
390
                method being called, the <span class="new_code">expectOnce()</span>
391
                expectation will trigger a test failure.
392
                In other words the mocks can detect the absence of
393
                behaviour as well as the presence.
394
            </p>
395
            <p>
396
                The mock objects in the SimpleTest suite can have arbitrary
397
                return values set, sequences of returns, return values
398
                selected according to the incoming arguments, sequences of
399
                parameter expectations and limits on the number of times
400
                a method is to be invoked.
401
            </p>
402
            <p>
403
                For this test to run the mock objects library must have been
404
                included in the test suite, say in <em>all_tests.php</em>.
405
            </p>
406
 
407
        <p>
408
<a class="target" name="web">
409
<h2>Web page testing</h2>
410
</a>
411
</p>
412
            <p>
413
                One of the requirements of web sites is that they produce web
414
                pages.
415
                If you are building a project top-down and you want to fully
416
                integrate testing along the way then you will want a way of
417
                automatically navigating a site and examining output for
418
                correctness.
419
                This is the job of a web tester.
420
            </p>
421
            <p>
422
                The web testing in SimpleTest is fairly primitive, there is
423
                no JavaScript for example.
424
                To give an idea here is a trivial example where a home
425
                page is fetched, from which we navigate to an "about"
426
                page and then test some client determined content.
427
<pre>
428
&lt;?php<strong>
429
require_once('simpletest/web_tester.php');</strong>
430
require_once('simpletest/reporter.php');
431
<strong>
432
class TestOfAbout extends WebTestCase {
433
 
434
    function setUp() {
435
        $this-&gt;get('http://test-server/index.php');
436
        $this-&gt;click('About');
437
    }
438
 
439
    function testSearchEngineOptimisations() {
440
        $this-&gt;assertTitle('A long title about us for search engines');
441
        $this-&gt;assertPattern('/a popular keyphrase/i');
442
    }
443
}</strong>
444
$test = &amp;new TestOfAbout();
445
$test-&gt;run(new HtmlReporter());
446
?&gt;
447
</pre>
448
                With this code as an acceptance test you can ensure that
449
                the content always meets the specifications of both the
450
                developers and the other project stakeholders.
451
            </p>
452
            <p>
453
                <a href="http://sourceforge.net/projects/simpletest/"><img src="http://sourceforge.net/sflogo.php?group_id=76550&amp;type=5" width="210" height="62" border="0" alt="SourceForge.net Logo"></a>
454
            </p>
455
 
456
    </div>
457
<div class="copyright">
458
            Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004
459
        </div>
460
</body>
461
</html>