summaryrefslogtreecommitdiff
path: root/test_tools/simpletest/docs/en/index.html
blob: 0479727272a52030a1d8f363f7e3fff58e4a8c22 (plain)
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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
        Download the Simple Test testing framework -
        Unit tests and mock objects for PHP
    </title>
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
</head>
<body>
<div class="menu_back">
<div class="menu">
<h2>
<span class="chosen">SimpleTest</span>
</h2>
<ul>
<li>
<a href="overview.html">Overview</a>
</li>
<li>
<a href="unit_test_documentation.html">Unit tester</a>
</li>
<li>
<a href="group_test_documentation.html">Group tests</a>
</li>
<li>
<a href="server_stubs_documentation.html">Server stubs</a>
</li>
<li>
<a href="mock_objects_documentation.html">Mock objects</a>
</li>
<li>
<a href="partial_mocks_documentation.html">Partial mocks</a>
</li>
<li>
<a href="reporter_documentation.html">Reporting</a>
</li>
<li>
<a href="expectation_documentation.html">Expectations</a>
</li>
<li>
<a href="web_tester_documentation.html">Web tester</a>
</li>
<li>
<a href="form_testing_documentation.html">Testing forms</a>
</li>
<li>
<a href="authentication_documentation.html">Authentication</a>
</li>
<li>
<a href="browser_documentation.html">Scriptable browser</a>
</li>
</ul>
</div>
</div>
<h1>Simple Test for PHP</h1>
<div class="content">
        
        
            <p>
                The following assumes that you are familiar with the concept
                of unit testing as well as the PHP web development language.
                It is a guide for the impatient new user of
                <a href="https://sourceforge.net/project/showfiles.php?group_id=76550">SimpleTest</a>.
                For fuller documentation, especially if you are new
                to unit testing see the ongoing
                <a href="unit_test_documentation.html">documentation</a>, and for
                example test cases see the
                <a href="http://www.lastcraft.com/first_test_tutorial.php">unit testing tutorial</a>.
            </p>
        
        <p>
<a class="target" name="unit">
<h2>Using the tester quickly</h2>
</a>
</p>
            <p>
                Amongst software testing tools, a unit tester is the one
                closest to the developer.
                In the context of agile development the test code sits right
                next to the source code as both are written simultaneously.
                In this context SimpleTest aims to be a complete PHP developer
                test solution and is called "Simple" because it
                should be easy to use and extend.
                It wasn't a good choice of name really.
                It includes all of the typical functions you would expect from
                <a href="http://www.junit.org/">JUnit</a> and the
                <a href="http://sourceforge.net/projects/phpunit/">PHPUnit</a>
                ports, but also adds
                <a href="http://www.mockobjects.com">mock objects</a>.
                It has some <a href="http://sourceforge.net/projects/jwebunit/">JWebUnit</a>
                functionality as well.
                This includes web page navigation, cookie testing and form submission.
            </p>
            <p>
                The quickest way to demonstrate is with an example.
            </p>
            <p>
                Let us suppose we are testing a simple file logging class called
                <span class="new_code">Log</span> in <em>classes/log.php</em>.
                We start by creating a test script which we will call
                <em>tests/log_test.php</em> and populate it as follows...
<pre>
<strong>&lt;?php
require_once('simpletest/unit_tester.php');
require_once('simpletest/reporter.php');
require_once('../classes/log.php');
?&gt;</strong>
</pre>
                Here the <em>simpletest</em> folder is either local or in the path.
                You would have to edit these locations depending on where you
                placed the toolset.
                Next we create a test case...
<pre>
&lt;?php
require_once('simpletest/unit_tester.php');
require_once('simpletest/reporter.php');
require_once('../classes/log.php');
<strong>
class TestOfLogging extends UnitTestCase {
}</strong>
?&gt;
</pre>
                Now we have five lines of scaffolding code and still no tests.
                However from this part on we get return on our investment very quickly.
                We'll assume that the <span class="new_code">Log</span> class
                takes the file name to write to in the constructor and we have
                a temporary folder in which to place this file...
<pre>
&lt;?php
require_once('simpletest/unit_tester.php');
require_once('simpletest/reporter.php');
require_once('../classes/log.php');

class TestOfLogging extends UnitTestCase {
    <strong>
    function testCreatingNewFile() {
        @unlink('/temp/test.log');
        $log = new Log('/temp/test.log');
        $this-&gt;assertFalse(file_exists('/temp/test.log'));
        $log-&gt;message('Should write this to a file');
        $this-&gt;assertTrue(file_exists('/temp/test.log'));
    }</strong>
}
?&gt;
</pre>
                When a test case runs it will search for any method that
                starts with the string <span class="new_code">test</span>
                and execute that method.
                We would normally have more than one test method of course.
                Assertions within the test methods trigger messages to the
                test framework which displays the result immediately.
                This immediate response is important, not just in the event
                of the code causing a crash, but also so that
                <span class="new_code">print</span> statements can display
                their content right next to the test case concerned.
            </p>
            <p>
                To see these results we have to actually run the tests.
                If this is the only test case we wish to run we can achieve
                it with...
<pre>
&lt;?php
require_once('simpletest/unit_tester.php');
require_once('simpletest/reporter.php');
require_once('../classes/log.php');

class TestOfLogging extends UnitTestCase {
    
    function testCreatingNewFile() {
        @unlink('/temp/test.log');
        $log = new Log('/temp/test.log');
        $this-&gt;assertFalse(file_exists('/temp/test.log'));
        $log-&gt;message('Should write this to a file');
        $this-&gt;assertTrue(file_exists('/temp/test.log'));
    }
}
<strong>
$test = &amp;new TestOfLogging();
$test-&gt;run(new HtmlReporter());</strong>
?&gt;
</pre>
            </p>
            <p>
                On failure the display looks like this...
                <div class="demo">
                    <h1>testoflogging</h1>
                    <span class="fail">Fail</span>: testcreatingnewfile-&gt;True assertion failed.<br>
                    <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
                    <strong>1</strong> passes and <strong>1</strong> fails.</div>
                </div>
                ...and if it passes like this...
                <div class="demo">
                    <h1>testoflogging</h1>
                    <div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
                    <strong>2</strong> passes and <strong>0</strong> fails.</div>
                </div>
				And if you get this...
                <div class="demo">
                    <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>
                </div>
				it means you're missing the <em>classes/Log.php</em> file that could look like...
<pre>
&lt;?php
class Log {
    
        function Log($file_path) {
        }

		function message() {
		}
}
?&gt;;
</pre>
            </p>
        
        <p>
<a class="target" name="group">
<h2>Building group tests</h2>
</a>
</p>
            <p>
                It is unlikely in a real application that we will only ever run
                one test case.
                This means that we need a way of grouping cases into a test
                script that can, if need be, run every test in the application.
            </p>
            <p>
                Our first step is to strip the includes and to undo our
                previous hack...
<pre>
&lt;?php<strong>
require_once('../classes/log.php');</strong>

class TestOfLogging extends UnitTestCase {
    
    function testCreatingNewFile() {
        @unlink('/temp/test.log');
        $log = new Log('/temp/test.log');
        $this-&gt;assertFalse(file_exists('/temp/test.log'));
        $log-&gt;message('Should write this to a file');
        $this-&gt;assertTrue(file_exists('/temp/test.log'));<strong>
    }
}
?&gt;</strong>
</pre>
                Next we create a new file called <em>tests/all_tests.php</em>
                and insert the following code...
<pre>
<strong>&lt;?php
require_once('simpletest/unit_tester.php');
require_once('simpletest/reporter.php');

$test = &amp;new GroupTest('All tests');
$test-&gt;addTestFile('log_test.php');
$test-&gt;run(new HtmlReporter());
?&gt;</strong>
</pre>
                The method <span class="new_code">GroupTest::addTestFile()</span>
                will include the test case file and read any new classes created
                that are descended from <span class="new_code">SimpleTestCase</span>, of which
                <span class="new_code">UnitTestCase</span> is one example.
                Just the class names are stored for now, so that the test runner
                can instantiate the class when it works its way
                through your test suite.
            </p>
            <p>
                For this to work properly the test case file should not blindly include
                any other test case extensions that do not actually run tests.
                This could result in extra test cases being counted during the test
                run.
                Hardly a major problem, but to avoid this inconvenience simply add
                a <span class="new_code">SimpleTestOptions::ignore()</span> directive
                somewhere in the test case file.
                Also the test case file should not have been included
                elsewhere or no cases will be added to this group test.
                This would be a more serious error as if the test case classes are
                already loaded by PHP the <span class="new_code">GroupTest::addTestFile()</span>
                method will not detect them.
            </p>
            <p>
                To display the results it is necessary only to invoke
                <em>tests/all_tests.php</em> from the web server.
            </p>
        
        <p>
<a class="target" name="mock">
<h2>Using mock objects</h2>
</a>
</p>
            <p>
                Let's move further into the future.
            </p>
            <p>
                Assume that our logging class is tested and completed.
                Assume also that we are testing another class that is
                required to write log messages, say a
                <span class="new_code">SessionPool</span>.
                We want to test a method that will probably end up looking
                like this...
<pre>
<strong>
class SessionPool {
    ...
    function logIn($username) {
        ...
        $this-&gt;_log-&gt;message("User $username logged in.");
        ...
    }
    ...
}
</strong>
</pre>
                In the spirit of reuse we are using our
                <span class="new_code">Log</span> class.
                A conventional test case might look like this...
<pre>
<strong>
&lt;?php
require_once('../classes/log.php');
require_once('../classes/session_pool.php');

class TestOfSessionLogging extends UnitTestCase {
    
    function setUp() {
        @unlink('/temp/test.log');
    }
    
    function tearDown() {
        @unlink('/temp/test.log');
    }
    
    function testLogInIsLogged() {
        $log = new Log('/temp/test.log');
        $session_pool = &amp;new SessionPool($log);
        $session_pool-&gt;logIn('fred');
        $messages = file('/temp/test.log');
        $this-&gt;assertEqual($messages[0], "User fred logged in.\n");
    }
}
?&gt;</strong>
</pre>
                This test case design is not all bad, but it could be improved.
                We are spending time fiddling with log files which are
                not part of our test. Worse, we have created close ties
                with the <span class="new_code">Log</span> class and
                this test.
                What if we don't use files any more, but use ths
                <em>syslog</em> library instead?
                Did you notice the extra carriage return in the message?
                Was that added by the logger?
                What if it also added a time stamp or other data?
            </p>
            <p>
                The only part that we really want to test is that a particular
                message was sent to the logger.
                We reduce coupling if we can pass in a fake logging class
                that simply records the message calls for testing, but
                takes no action.
                It would have to look exactly like our original though.
            </p>
            <p>
                If the fake object doesn't write to a file then we save on deleting
                the file before and after each test. We could save even more
                test code if the fake object would kindly run the assertion for us.
            <p>
            </p>
                Too good to be true?
                Luckily we can create such an object easily...
<pre>
&lt;?php
require_once('../classes/log.php');
require_once('../classes/session_pool.php');<strong>
Mock::generate('Log');</strong>

class TestOfSessionLogging extends UnitTestCase {
    
    function testLogInIsLogged() {<strong>
        $log = &amp;new MockLog($this);
        $log-&gt;expectOnce('message', array('User fred logged in.'));</strong>
        $session_pool = &amp;new SessionPool($log);
        $session_pool-&gt;logIn('fred');<strong>
        $log-&gt;tally();</strong>
    }
}
?&gt;
</pre>
                The <span class="new_code">tally()</span> call is needed to
                tell the mock object that time is up for the expected call
                count.
                Without it the mock would wait forever for the method
                call to come in without ever actually notifying the test case.
                The other test will be triggered when the call to
                <span class="new_code">message()</span> is invoked on the
                <span class="new_code">MockLog</span> object.
                The mock call will trigger a parameter comparison and then send the
                resulting pass or fail event to the test display.
                Wildcards can be included here too so as to prevent tests
                becoming too specific.
            </p>
            <p>
                The mock objects in the SimpleTest suite can have arbitrary
                return values set, sequences of returns, return values
                selected according to the incoming arguments, sequences of
                parameter expectations and limits on the number of times
                a method is to be invoked.
            </p>
            <p>
                For this test to run the mock objects library must have been
                included in the test suite, say in <em>all_tests.php</em>.
            </p>
        
        <p>
<a class="target" name="web">
<h2>Web page testing</h2>
</a>
</p>
            <p>
                One of the requirements of web sites is that they produce web
                pages.
                If you are building a project top-down and you want to fully
                integrate testing along the way then you will want a way of
                automatically navigating a site and examining output for
                correctness.
                This is the job of a web tester.
            </p>
            <p>
                The web testing in SimpleTest is fairly primitive, there is
                no JavaScript for example.
                To give an idea here is a trivial example where a home
                page is fetched, from which we navigate to an "about"
                page and then test some client determined content.
<pre>
&lt;?php<strong>
require_once('simpletest/web_tester.php');</strong>
require_once('simpletest/reporter.php');
<strong>
class TestOfAbout extends WebTestCase {
    
    function setUp() {
        $this-&gt;get('http://test-server/index.php');
        $this-&gt;clickLink('About');
    }
    
    function testSearchEngineOptimisations() {
        $this-&gt;assertTitle('A long title about us for search engines');
        $this-&gt;assertWantedPattern('/a popular keyphrase/i');
    }
}</strong>
$test = &amp;new TestOfAbout();
$test-&gt;run(new HtmlReporter());
?&gt;
</pre>
                With this code as an acceptance test you can ensure that
                the content always meets the specifications of both the
                developers and the other project stakeholders.
            </p>
            <p>
                <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>
            </p>
        
    </div>
<div class="copyright">
            Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004
        </div>
</body>
</html>