source: trunk/server/www/vendors/simpletest/docs/en/partial_mocks_documentation.html @ 6

Last change on this file since 6 was 6, checked in by sander, 11 years ago

Added SimpleTest? test framework

File size: 16.2 KB
Line 
1<html>
2<head>
3<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
4<title>SimpleTest for PHP partial mocks documentation</title>
5<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
6</head>
7<body>
8<div class="menu_back"><div class="menu">
9<a href="index.html">SimpleTest</a>
10                |
11                <a href="overview.html">Overview</a>
12                |
13                <a href="unit_test_documentation.html">Unit tester</a>
14                |
15                <a href="group_test_documentation.html">Group tests</a>
16                |
17                <a href="mock_objects_documentation.html">Mock objects</a>
18                |
19                <span class="chosen">Partial mocks</span>
20                |
21                <a href="reporter_documentation.html">Reporting</a>
22                |
23                <a href="expectation_documentation.html">Expectations</a>
24                |
25                <a href="web_tester_documentation.html">Web tester</a>
26                |
27                <a href="form_testing_documentation.html">Testing forms</a>
28                |
29                <a href="authentication_documentation.html">Authentication</a>
30                |
31                <a href="browser_documentation.html">Scriptable browser</a>
32</div></div>
33<h1>Partial mock objects documentation</h1>
34        This page...
35        <ul>
36<li>
37            <a href="#inject">The mock injection problem</a>.
38        </li>
39<li>
40            Moving creation to a <a href="#creation">protected factory</a> method.
41        </li>
42<li>
43            <a href="#partial">Partial mocks</a> generate subclasses.
44        </li>
45<li>
46            Partial mocks <a href="#less">test less than a class</a>.
47        </li>
48</ul>
49<div class="content">
50       
51            <p>
52                A partial mock is simply a pattern to alleviate a specific problem
53                in testing with mock objects,
54                that of getting mock objects into tight corners.
55                It's quite a limited tool and possibly not even a good idea.
56                It is included with SimpleTest because I have found it useful
57                on more than one occasion and has saved a lot of work at that point.
58            </p>
59       
60        <p><a class="target" name="inject"><h2>The mock injection problem</h2></a></p>
61            <p>
62                When one object uses another it is very simple to just pass a mock
63                version in already set up with its expectations.
64                Things are rather tricker if one object creates another and the
65                creator is the one you want to test.
66                This means that the created object should be mocked, but we can
67                hardly tell our class under test to create a mock instead.
68                The tested class doesn't even know it is running inside a test
69                after all.
70            </p>
71            <p>
72                For example, suppose we are building a telnet client and it
73                needs to create a network socket to pass its messages.
74                The connection method might look something like...
75<pre>
76<strong>&lt;?php
77require_once('socket.php');
78   
79class Telnet {
80    ...
81    function &amp;connect($ip, $port, $username, $password) {
82        $socket = &amp;new Socket($ip, $port);
83        $socket-&gt;read( ... );
84        ...
85    }
86}
87?&gt;</strong>
88</pre>
89                We would really like to have a mock object version of the socket
90                here, what can we do?
91            </p>
92            <p>
93                The first solution is to pass the socket in as a parameter,
94                forcing the creation up a level.
95                Having the client handle this is actually a very good approach
96                if you can manage it and should lead to factoring the creation from
97                the doing.
98                In fact, this is one way in which testing with mock objects actually
99                forces you to code more tightly focused solutions.
100                They improve your programming.
101            </p>
102            <p>
103                Here this would be...
104<pre>
105&lt;?php
106require_once('socket.php');
107   
108class Telnet {
109    ...
110    <strong>function &amp;connect(&amp;$socket, $username, $password) {
111        $socket-&gt;read( ... );
112        ...
113    }</strong>
114}
115?&gt;
116</pre>
117                This means that the test code is typical for a test involving
118                mock objects.
119<pre>
120class TelnetTest extends UnitTestCase {
121    ...
122    function testConnection() {<strong>
123        $socket = &amp;new MockSocket($this);
124        ...
125        $telnet = &amp;new Telnet();
126        $telnet-&gt;connect($socket, 'Me', 'Secret');
127        ...</strong>
128    }
129}
130</pre>
131                It is pretty obvious though that one level is all you can go.
132                You would hardly want your top level application creating
133                every low level file, socket and database connection ever
134                needed.
135                It wouldn't know the constructor parameters anyway.
136            </p>
137            <p>
138                The next simplest compromise is to have the created object passed
139                in as an optional parameter...
140<pre>
141&lt;?php
142require_once('socket.php');
143   
144class Telnet {
145    ...<strong>
146    function &amp;connect($ip, $port, $username, $password, $socket = false) {
147        if (!$socket) {
148            $socket = &amp;new Socket($ip, $port);
149        }
150        $socket-&gt;read( ... );</strong>
151        ...
152        return $socket;
153    }
154}
155?&gt;
156</pre>
157                For a quick solution this is usually good enough.
158                The test now looks almost the same as if the parameter
159                was formally passed...
160<pre>
161class TelnetTest extends UnitTestCase {
162    ...
163    function testConnection() {<strong>
164        $socket = &amp;new MockSocket($this);
165        ...
166        $telnet = &amp;new Telnet();
167        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret', &amp;$socket);
168        ...</strong>
169    }
170}
171</pre>
172                The problem with this approach is its untidiness.
173                There is test code in the main class and parameters passed
174                in the test case that are never used.
175                This is a quick and dirty approach, but nevertheless effective
176                in most situations.
177            </p>
178            <p>
179                The next method is to pass in a factory object to do the creation...
180<pre>
181&lt;?php
182require_once('socket.php');
183   
184class Telnet {<strong>
185   function Telnet(&amp;$network) {
186        $this-&gt;_network = &amp;$network;
187    }</strong>
188    ...
189    function &amp;connect($ip, $port, $username, $password) {<strong>
190        $socket = &amp;$this-&gt;_network-&gt;createSocket($ip, $port);
191        $socket-&gt;read( ... );</strong>
192        ...
193        return $socket;
194    }
195}
196?&gt;
197</pre>
198                This is probably the most highly factored answer as creation
199                is now moved into a small specialist class.
200                The networking factory can now be tested separately, but mocked
201                easily when we are testing the telnet class...
202<pre>
203class TelnetTest extends UnitTestCase {
204    ...
205    function testConnection() {<strong>
206        $socket = &amp;new MockSocket($this);
207        ...
208        $network = &amp;new MockNetwork($this);
209        $network-&gt;setReturnReference('createSocket', $socket);
210        $telnet = &amp;new Telnet($network);
211        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
212        ...</strong>
213    }
214}
215</pre>
216                The downside is that we are adding a lot more classes to the
217                library.
218                Also we are passing a lot of factories around which will
219                make the code a little less intuitive.
220                The most flexible solution, but the most complex.
221            </p>
222            <p>
223                Is there a middle ground?
224            </p>
225       
226        <p><a class="target" name="creation"><h2>Protected factory method</h2></a></p>
227            <p>
228                There is a way we can circumvent the problem without creating
229                any new application classes, but it involves creating a subclass
230                when we do the actual testing.
231                Firstly we move the socket creation into its own method...
232<pre>
233&lt;?php
234require_once('socket.php');
235   
236class Telnet {
237    ...
238    function &amp;connect($ip, $port, $username, $password) {<strong>
239        $socket = &amp;$this-&gt;_createSocket($ip, $port);</strong>
240        $socket-&gt;read( ... );
241        ...
242    }<strong>
243       
244    function &amp;_createSocket($ip, $port) {
245        return new Socket($ip, $port);
246    }</strong>
247}
248?&gt;
249</pre>
250                This is the only change we make to the application code.
251            </p>
252            <p>
253                For the test case we have to create a subclass so that
254                we can intercept the socket creation...
255<pre>
256<strong>class TelnetTestVersion extends Telnet {
257    var $_mock;
258   
259    function TelnetTestVersion(&amp;$mock) {
260        $this-&gt;_mock = &amp;$mock;
261        $this-&gt;Telnet();
262    }
263   
264    function &amp;_createSocket() {
265        return $this-&gt;_mock;
266    }
267}</strong>
268</pre>
269                Here I have passed the mock in the constructor, but a
270                setter would have done just as well.
271                Note that the mock was set into the object variable
272                before the constructor was chained.
273                This is necessary in case the constructor calls
274                <span class="new_code">connect()</span>.
275                Otherwise it could get a null value from
276                <span class="new_code">_createSocket()</span>.
277            </p>
278            <p>
279                After the completion of all of this extra work the
280                actual test case is fairly easy.
281                We just test our new class instead...
282<pre>
283class TelnetTest extends UnitTestCase {
284    ...
285    function testConnection() {<strong>
286        $socket = &amp;new MockSocket($this);
287        ...
288        $telnet = &amp;new TelnetTestVersion($socket);
289        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
290        ...</strong>
291    }
292}
293</pre>
294                The new class is very simple of course.
295                It just sets up a return value, rather like a mock.
296                It would be nice if it also checked the incoming parameters
297                as well.
298                Just like a mock.
299                It seems we are likely to do this often, can
300                we automate the subclass creation?
301            </p>
302       
303        <p><a class="target" name="partial"><h2>A partial mock</h2></a></p>
304            <p>
305                Of course the answer is "yes" or I would have stopped writing
306                this by now!
307                The previous test case was a lot of work, but we can
308                generate the subclass using a similar approach to the mock objects.
309            </p>
310            <p>
311                Here is the partial mock version of the test...
312<pre>
313<strong>Mock::generatePartial(
314        'Telnet',
315        'TelnetTestVersion',
316        array('_createSocket'));</strong>
317
318class TelnetTest extends UnitTestCase {
319    ...
320    function testConnection() {<strong>
321        $socket = &amp;new MockSocket($this);
322        ...
323        $telnet = &amp;new TelnetTestVersion($this);
324        $telnet-&gt;setReturnReference('_createSocket', $socket);
325        $telnet-&gt;Telnet();
326        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
327        ...</strong>
328    }
329}
330</pre>
331                The partial mock is a subclass of the original with
332                selected methods "knocked out" with test
333                versions.
334                The <span class="new_code">generatePartial()</span> call
335                takes three parameters: the class to be subclassed,
336                the new test class name and a list of methods to mock.
337            </p>
338            <p>
339                Instantiating the resulting objects is slightly tricky.
340                The only constructor parameter of a partial mock is
341                the unit tester reference.
342                As with the normal mock objects this is needed for sending
343                test results in response to checked expectations.
344            </p>
345            <p>
346                The original constructor is not run yet.
347                This is necessary in case the constructor is going to
348                make use of the as yet unset mocked methods.
349                We set any return values at this point and then run the
350                constructor with its normal parameters.
351                This three step construction of "new", followed
352                by setting up the methods, followed by running the constructor
353                proper is what distinguishes the partial mock code.
354            </p>
355            <p>
356                Apart from construction, all of the mocked methods have
357                the same features as mock objects and all of the unmocked
358                methods behave as before.
359                We can set expectations very easily...
360<pre>
361class TelnetTest extends UnitTestCase {
362    ...
363    function testConnection() {
364        $socket = &amp;new MockSocket($this);
365        ...
366        $telnet = &amp;new TelnetTestVersion($this);
367        $telnet-&gt;setReturnReference('_createSocket', $socket);<strong>
368        $telnet-&gt;expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
369        $telnet-&gt;Telnet();
370        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
371        ...<strong>
372        $telnet-&gt;tally();</strong>
373    }
374}
375</pre>
376            </p>
377       
378        <p><a class="target" name="less"><h2>Testing less than a class</h2></a></p>
379            <p>
380                The mocked out methods don't have to be factory methods,
381                they could be any sort of method.
382                In this way partial mocks allow us to take control of any part of
383                a class except the constructor.
384                We could even go as far as to mock every method
385                except one we actually want to test.
386            </p>
387            <p>
388                This last situation is all rather hypothetical, as I haven't
389                tried it.
390                I am open to the possibility, but a little worried that
391                forcing object granularity may be better for the code quality.
392                I personally use partial mocks as a way of overriding creation
393                or for occasional testing of the TemplateMethod pattern.
394            </p>
395            <p>
396                It's all going to come down to the coding standards of your
397                project to decide which mechanism you use.
398            </p>
399       
400    </div>
401        References and related information...
402        <ul>
403<li>
404            SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
405        </li>
406<li>
407            <a href="http://simpletest.org/api/">Full API for SimpleTest</a>
408            from the PHPDoc.
409        </li>
410<li>
411            The protected factory is described in
412            <a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">this paper from IBM</a>.
413            This is the only formal comment I have seen on this problem.
414        </li>
415</ul>
416<div class="menu_back"><div class="menu">
417<a href="index.html">SimpleTest</a>
418                |
419                <a href="overview.html">Overview</a>
420                |
421                <a href="unit_test_documentation.html">Unit tester</a>
422                |
423                <a href="group_test_documentation.html">Group tests</a>
424                |
425                <a href="mock_objects_documentation.html">Mock objects</a>
426                |
427                <span class="chosen">Partial mocks</span>
428                |
429                <a href="reporter_documentation.html">Reporting</a>
430                |
431                <a href="expectation_documentation.html">Expectations</a>
432                |
433                <a href="web_tester_documentation.html">Web tester</a>
434                |
435                <a href="form_testing_documentation.html">Testing forms</a>
436                |
437                <a href="authentication_documentation.html">Authentication</a>
438                |
439                <a href="browser_documentation.html">Scriptable browser</a>
440</div></div>
441<div class="copyright">
442            Copyright<br>Marcus Baker 2006
443        </div>
444</body>
445</html>
Note: See TracBrowser for help on using the repository browser.