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>SimpleTest for PHP server stubs documentation</title>
5
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
6
</head>
7
<body>
8
<div class="menu_back">
9
<div class="menu">
10
<h2>
11
<a href="index.html">SimpleTest</a>
12
</h2>
13
<ul>
14
<li>
15
<a href="overview.html">Overview</a>
16
</li>
17
<li>
18
<a href="unit_test_documentation.html">Unit tester</a>
19
</li>
20
<li>
21
<a href="group_test_documentation.html">Group tests</a>
22
</li>
23
<li>
24
<span class="chosen">Server stubs</span>
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>Server stubs documentation</h1>
54
<div class="content">
55
        <p>
56
<a class="target" name="what">
57
<h2>What are server stubs?</h2>
58
</a>
59
</p>
60
            <p>
61
                This was originally a pattern named by Robert Binder (Testing
62
                object-oriented systems: models, patterns, and tools,
63
                Addison-Wesley) in 1999.
64
                A server stub is a simulation of an object or component.
65
                It should exactly replace a component in a system for test
66
                or prototyping purposes, but remain lightweight.
67
                This allows tests to run more quickly, or if the simulated
68
                class has not been written, to run at all.
69
            </p>
70
 
71
        <p>
72
<a class="target" name="creation">
73
<h2>Creating server stubs</h2>
74
</a>
75
</p>
76
            <p>
77
                All we need is an existing class, say a database connection
78
                that looks like this...
79
<pre>
80
<strong>class DatabaseConnection {
81
    function DatabaseConnection() {
82
    }
83
 
84
    function query() {
85
    }
86
 
87
    function selectQuery() {
88
    }
89
}</strong>
90
</pre>
91
                The class does not need to have been implemented yet.
92
                To create a stub version of the class we need to include the
93
                server stub library and run the generator...
94
<pre>
95
<strong>require_once('simpletest/mock_objects.php');
96
require_once('database_connection.php');
97
Stub::generate('DatabaseConnection');</strong>
98
</pre>
99
                This generates a clone class called
100
                <span class="new_code">StubDatabaseConnection</span>.
101
                We can now create instances of the new class within
102
                our prototype script...
103
<pre>
104
require_once('simpletest/mock_objects.php');
105
require_once('database_connection.php');
106
Stub::generate('DatabaseConnection');
107
<strong>
108
$connection = new StubDatabaseConnection();
109
</strong>
110
</pre>
111
                The stub version of a class has all the methods of the original
112
                so that operations like
113
                <span class="new_code">$connection-&gt;query()</span> are still
114
                legal.
115
                The return value will be <span class="new_code">null</span>,
116
                but we can change that with...
117
<pre>
118
<strong>$connection-&gt;setReturnValue('query', 37)</strong>
119
</pre>
120
                Now every time we call
121
                <span class="new_code">$connection-&gt;query()</span> we get
122
                the result of 37.
123
                We can set the return value to anything, say a hash of
124
                imaginary database results or a list of persistent objects.
125
                Parameters are irrelevant here, we always get the same
126
                values back each time once they have been set up this way.
127
                That may not sound like a convincing replica of a
128
                database connection, but for the half a dozen lines of
129
                a test method it is usually all you need.
130
            </p>
131
 
132
        <p>
133
<a class="target" name="patterns">
134
<h2>Simulation patterns</h2>
135
</a>
136
</p>
137
            <p>
138
                Things aren't always that simple though.
139
                One common problem is iterators, where constantly returning
140
                the same value could cause an endless loop in the object
141
                being tested.
142
                For these we need to set up sequences of values.
143
                Let's say we have a simple iterator that looks like this...
144
<pre>
145
class Iterator {
146
    function Iterator() {
147
    }
148
 
149
    function next() {
150
    }
151
}
152
</pre>
153
                This is about the simplest iterator you could have.
154
                Assuming that this iterator only returns text until it
155
                reaches the end, when it returns false, we can simulate it
156
                with...
157
<pre>
158
<strong>Stub::generate('Iterator');
159
 
160
$iterator = new StubIterator();
161
$iterator-&gt;setReturnValue('next', false);
162
$iterator-&gt;setReturnValueAt(0, 'next', 'First string');
163
$iterator-&gt;setReturnValueAt(1, 'next', 'Second string');</strong>
164
</pre>
165
                When <span class="new_code">next()</span> is called on the
166
                stub iterator it will first return "First string",
167
                on the second call "Second string" will be returned
168
                and on any other call <span class="new_code">false</span> will
169
                be returned.
170
                The sequenced return values take precedence over the constant
171
                return value.
172
                The constant one is a kind of default if you like.
173
            </p>
174
            <p>
175
                Another tricky situation is an overloaded
176
                <span class="new_code">get()</span> operation.
177
                An example of this is an information holder with name/value pairs.
178
                Say we have a configuration class like...
179
<pre>
180
class Configuration {
181
    function Configuration() {
182
    }
183
 
184
    function getValue($key) {
185
    }
186
}
187
</pre>
188
                This is a classic situation for using stub objects as
189
                actual configuration will vary from machine to machine,
190
                hardly helping the reliability of our tests if we use it
191
                directly.
192
                The problem though is that all the data comes through the
193
                <span class="new_code">getValue()</span> method and yet
194
                we want different results for different keys.
195
                Luckily the stubs have a filter system...
196
<pre>
197
<strong>Stub::generate('Configuration');
198
 
199
$config = &amp;new StubConfiguration();
200
$config-&gt;setReturnValue('getValue', 'primary', array('db_host'));
201
$config-&gt;setReturnValue('getValue', 'admin', array('db_user'));
202
$config-&gt;setReturnValue('getValue', 'secret', array('db_password'));</strong>
203
</pre>
204
                The extra parameter is a list of arguments to attempt
205
                to match.
206
                In this case we are trying to match only one argument which
207
                is the look up key.
208
                Now when the server stub has the
209
                <span class="new_code">getValue()</span> method invoked
210
                like this...
211
<pre>
212
$config-&gt;getValue('db_user');
213
</pre>
214
                ...it will return "admin".
215
                It finds this by attempting to match the calling arguments
216
                to its list of returns one after another until
217
                a complete match is found.
218
            </p>
219
            <p>
220
                You can set a default argument argument like so...
221
<pre>
222
<strong>
223
$config-&gt;setReturnValue('getValue', false, array('*'));</strong>
224
</pre>
225
                This is not the same as setting the return value without
226
                any argument requirements like this...
227
<pre>
228
<strong>
229
$config-&gt;setReturnValue('getValue', false);</strong>
230
</pre>
231
                In the first case it will accept any single argument,
232
                but exactly one is required.
233
                In the second case any number of arguments will do and
234
                it acts as a catchall after all other matches.
235
                Note that if we add further single parameter options after
236
                the wildcard in the first case, they will be ignored as the wildcard
237
                will match first.
238
                With complex parameter lists the ordering could be important
239
                or else desired matches could be masked by earlier wildcard
240
                ones.
241
                Declare the most specific matches first if you are not sure.
242
            </p>
243
            <p>
244
                There are times when you want a specific object to be
245
                dished out by the stub rather than just a copy.
246
                The PHP copy semantics force us to use a different method
247
                for this.
248
                You might be simulating a container for example...
249
<pre>
250
class Thing {
251
}
252
 
253
class Vector {
254
    function Vector() {
255
    }
256
 
257
    function get($index) {
258
    }
259
}
260
</pre>
261
                In this case you can set a reference into the stub's
262
                return list...
263
<pre>
264
Stub::generate('Vector');
265
 
266
$thing = new Thing();<strong>
267
$vector = &amp;new StubVector();
268
$vector-&gt;setReturnReference('get', $thing, array(12));</strong>
269
</pre>
270
                With this arrangement you know that every time
271
                <span class="new_code">$vector-&gt;get(12)</span> is
272
                called it will return the same
273
                <span class="new_code">$thing</span> each time.
274
            </p>
275
            <p>
276
                These three factors, timing, parameters and whether to copy,
277
                can be combined orthogonally.
278
                For example...
279
<pre>
280
$complex = &amp;new StubComplexThing();
281
$stuff = new Stuff();<strong>
282
$complex-&gt;setReturnReferenceAt(3, 'get', $stuff, array('*', 1));</strong>
283
</pre>
284
                This will return the <span class="new_code">$stuff</span> only on the third
285
                call and only if two parameters were set the second of
286
                which must be the integer 1.
287
                That should cover most simple prototyping situations.
288
            </p>
289
            <p>
290
                A final tricky case is one object creating another, known
291
                as a factory pattern.
292
                Suppose that on a successful query to our imaginary
293
                database, a result set is returned as an iterator with
294
                each call to <span class="new_code">next()</span> giving
295
                one row until false.
296
                This sounds like a simulation nightmare, but in fact it can all
297
                be stubbed using the mechanics above.
298
            </p>
299
            <p>
300
                Here's how...
301
<pre>
302
Stub::generate('DatabaseConnection');
303
Stub::generate('ResultIterator');
304
 
305
class DatabaseTest extends UnitTestCase {
306
 
307
    function testUserFinder() {<strong>
308
        $result = &amp;new StubResultIterator();
309
        $result-&gt;setReturnValue('next', false);
310
        $result-&gt;setReturnValueAt(0, 'next', array(1, 'tom'));
311
        $result-&gt;setReturnValueAt(1, 'next', array(3, 'dick'));
312
        $result-&gt;setReturnValueAt(2, 'next', array(6, 'harry'));
313
 
314
        $connection = &amp;new StubDatabaseConnection();
315
        $connection-&gt;setReturnValue('query', false);
316
        $connection-&gt;setReturnReference(
317
                'query',
318
                $result,
319
                array('select id, name from users'));</strong>
320
 
321
        $finder = &amp;new UserFinder($connection);
322
        $this-&gt;assertIdentical(
323
                $finder-&gt;findNames(),
324
                array('tom', 'dick', 'harry'));
325
    }
326
}
327
</pre>
328
                Now only if our
329
                <span class="new_code">$connection</span> is called with the correct
330
                <span class="new_code">query()</span> will the
331
                <span class="new_code">$result</span> be returned that is
332
                itself exhausted after the third call to <span class="new_code">next()</span>.
333
                This should be enough
334
                information for our <span class="new_code">UserFinder</span> class,
335
                the class actually
336
                being tested here, to come up with goods.
337
                A very precise test and not a real database in sight.
338
            </p>
339
 
340
        <p>
341
<a class="target" name="options">
342
<h2>Stub creation options</h2>
343
</a>
344
</p>
345
            <p>
346
                There are some additional options when creating stubs.
347
                At the generation stage we can change the class name...
348
<pre>
349
<strong>Stub::generate('Iterator', 'MyStubIterator');
350
$iterator = &amp;new MyStubIterator();
351
</strong>
352
</pre>
353
                This is not very useful in itself as there would be no difference
354
                in this class and the default except for the name.
355
                However we can also add additional methods not found in the
356
                original interface...
357
<pre>
358
class Iterator {
359
}
360
<strong>Stub::generate('Iterator', 'PrototypeIterator', array('next', 'isError'));
361
$iterator = &amp;new PrototypeIterator();
362
$iterator-&gt;setReturnValue('next', 0);
363
</strong>
364
</pre>
365
                The <span class="new_code">next()</span> and
366
                <span class="new_code">isError()</span> methods can now have
367
                return values set just as if they existed in the original class.
368
            </p>
369
            <p>
370
                One other esoteric way of customising the stubs is to change
371
                the default wildcard used for parameter matching.
372
<pre>
373
<strong>Stub::generate('Connection');
374
$iterator = &amp;new StubConnection('wild');
375
$iterator-&gt;setReturnValue('query', array('id' =&gt; 33), array('wild'));
376
</strong>
377
</pre>
378
                The only reason to do this is if you genuinely wanted to test
379
                against the literal string "*" and didn't want it
380
                interpreted as "any".
381
            </p>
382
 
383
    </div>
384
<div class="copyright">
385
            Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004
386
        </div>
387
</body>
388
</html>