aboutsummaryrefslogtreecommitdiff
path: root/vendors/simpletest/docs/fr/partial_mocks_documentation.html
blob: 32a39c760044834dd1ef94661ec73ebfe7f083a4 (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
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Documentation SimpleTest : les objets fantaisie partiels</title>
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
</head>
<body>
<div class="menu_back"><div class="menu">
<a href="index.html">SimpleTest</a>
                |
                <a href="overview.html">Overview</a>
                |
                <a href="unit_test_documentation.html">Unit tester</a>
                |
                <a href="group_test_documentation.html">Group tests</a>
                |
                <a href="mock_objects_documentation.html">Mock objects</a>
                |
                <a href="partial_mocks_documentation.html">Partial mocks</a>
                |
                <a href="reporter_documentation.html">Reporting</a>
                |
                <a href="expectation_documentation.html">Expectations</a>
                |
                <a href="web_tester_documentation.html">Web tester</a>
                |
                <a href="form_testing_documentation.html">Testing forms</a>
                |
                <a href="authentication_documentation.html">Authentication</a>
                |
                <a href="browser_documentation.html">Scriptable browser</a>
</div></div>
<h1>Documentation sur les objets fantaisie partiels</h1>
        This page...
        <ul>
<li>
            <a href="#injection">Le problème de l'injection d'un objet fantaisie</a>.
        </li>
<li>
            Déplacer la création vers une méthode <a href="#creation">fabrique protégée</a>.
        </li>
<li>
            <a href="#partiel">L'objet fantaisie partiel</a> génère une sous-classe.
        </li>
<li>
            Les objets fantaisie partiels <a href="#moins">testent moins qu'une classe</a>.
        </li>
</ul>
<div class="content">
        
            <p>
                Un objet fantaisie partiel n'est ni plus ni moins
                qu'un modèle de conception pour soulager un problème spécifique
                du test avec des objets fantaisie, celui de placer
                des objets fantaisie dans des coins serrés.
                Il s'agit d'un outil assez limité et peut-être même
                une idée pas si bonne que ça. Elle est incluse dans SimpleTest
                pour la simple raison que je l'ai trouvée utile
                à plus d'une occasion et qu'elle m'a épargnée
                pas mal de travail dans ces moments-là.
            </p>
        
        <p><a class="target" name="injection"><h2>Le problème de l'injection dans un objet fantaisie</h2></a></p>
            <p>
                Quand un objet en utilise un autre il est très simple
                d'y faire circuler une version fantaisie déjà prête
                avec ses attentes. Les choses deviennent un peu plus délicates
                si un objet en crée un autre et que le créateur est celui
                que l'on souhaite tester. Cela revient à dire que l'objet
                créé devrait être une fantaisie, mais nous pouvons
                difficilement dire à notre classe sous test de créer
                un objet fantaisie plutôt qu'un "vrai" objet.
                La classe testée ne sait même pas qu'elle travaille dans un environnement de test.
            </p>
            <p>
                Par exemple, supposons que nous sommes en train
                de construire un client telnet et qu'il a besoin
                de créer une socket réseau pour envoyer ses messages.
                La méthode de connexion pourrait ressemble à quelque chose comme...
<pre>
<strong>&lt;?php
require_once('socket.php');

class Telnet {
    ...
    function &amp;connect($ip, $port, $username, $password) {
        $socket = &amp;new Socket($ip, $port);
        $socket-&gt;read( ... );
        ...
    }
}
?&gt;</strong>
</pre>
                Nous voudrions vraiment avoir une version fantaisie
                de l'objet socket, que pouvons nous faire ?
            </p>
            <p>
                La première solution est de passer la socket en
                tant que paramètre, ce qui force la création
                au niveau inférieur. Charger le client de cette tâche
                est effectivement une bonne approche si c'est possible
                et devrait conduire à un remaniement -- de la création
                à partir de l'action. En fait, c'est là une des manières
                avec lesquels tester en s'appuyant sur des objets fantaisie
                vous force à coder des solutions plus resserrées sur leur objectif.
                Ils améliorent votre programmation.
            </p>
            <p>
                Voici ce que ça devrait être...
<pre>
&lt;?php
require_once('socket.php');

class Telnet {
    ...
    <strong>function &amp;connect(&amp;$socket, $username, $password) {
        $socket-&gt;read( ... );
        ...
    }</strong>
}
?&gt;
</pre>
                Sous-entendu, votre code de test est typique d'un cas
                de test avec un objet fantaisie.
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new Telnet();
        $telnet-&gt;connect($socket, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                C'est assez évident que vous ne pouvez descendre que d'un niveau.
                Vous ne voudriez pas que votre application de haut niveau
                crée tous les fichiers de bas niveau, sockets et autres connexions
                à la base de données dont elle aurait besoin.
                Elle ne connaîtrait pas les paramètres du constructeur de toute façon.
            </p>
            <p>
                La solution suivante est de passer l'objet créé sous la forme
                d'un paramètre optionnel...
<pre>
&lt;?php
require_once('socket.php');

class Telnet {
    ...<strong>
    function &amp;connect($ip, $port, $username, $password, $socket = false) {
        if (!$socket) {
            $socket = &amp;new Socket($ip, $port);
        }
        $socket-&gt;read( ... );</strong>
        ...
        return $socket;
    }
}
?&gt;
</pre>
                Pour une solution rapide, c'est généralement suffisant.
                Ensuite le test est très similaire : comme si le paramètre
                était transmis formellement...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret', &amp;$socket);
        ...</strong>
    }
}
</pre>
                Le problème de cette approche tient dans son manque de netteté.
                Il y a du code de test dans la classe principale et aussi
                des paramètres transmis dans le scénario de test
                qui ne sont jamais utilisés. Il s'agit là d'une approche
                rapide et sale, mais qui ne reste pas moins efficace
                dans la plupart des situations.
            </p>
            <p>
                Une autre solution encore est de laisser un objet fabrique
                s'occuper de la création...
<pre>
&lt;?php
require_once('socket.php');

class Telnet {<strong>
    function Telnet(&amp;$network) {
        $this-&gt;_network = &amp;$network;
    }</strong>
    ...
    function &amp;connect($ip, $port, $username, $password) {<strong>
        $socket = &amp;$this-&gt;_network-&gt;createSocket($ip, $port);
        $socket-&gt;read( ... );</strong>
        ...
        return $socket;
    }
}
?&gt;
</pre>
                Il s'agit là probablement de la réponse la plus travaillée
                étant donné que la création est maintenant située
                dans une petite classe spécialisée. La fabrique réseau
                peut être testée séparément et utilisée en tant que fantaisie
                quand nous testons la classe telnet...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $network = &amp;new MockNetwork($this);
        $network-&gt;setReturnReference('createSocket', $socket);
        $telnet = &amp;new Telnet($network);
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                Le problème reste que nous ajoutons beaucoup de classes
                à la bibliothèque. Et aussi que nous utilisons beaucoup
                de fabriques ce qui rend notre code un peu moins intuitif.
                La solution la plus flexible, mais aussi la plus complexe.
            </p>
            <p>
                Peut-on trouver un juste milieu ?
            </p>
        
        <p><a class="target" name="creation"><h2>Méthode fabrique protégée</h2></a></p>
            <p>
                Il existe une technique pour palier à ce problème
                sans créer de nouvelle classe dans l'application;
                par contre elle induit la création d'une sous-classe au moment du test.
                Premièrement nous déplaçons la création de la socket dans sa propre méthode...
<pre>
&lt;?php
require_once('socket.php');

class Telnet {
    ...
    function &amp;connect($ip, $port, $username, $password) {<strong>
        $socket = &amp;$this-&gt;_createSocket($ip, $port);</strong>
        $socket-&gt;read( ... );
        ...
    }<strong>
    
    function &amp;_createSocket($ip, $port) {
        return new Socket($ip, $port);
    }</strong>
}
?&gt;
</pre>
                Il s'agit là de la seule modification dans le code de l'application.
            </p>
            <p>
                Pour le scénario de test, nous devons créer
                une sous-classe de manière à intercepter la création de la socket...
<pre>
<strong>class TelnetTestVersion extends Telnet {
    var $_mock;
    
    function TelnetTestVersion(&amp;$mock) {
        $this-&gt;_mock = &amp;$mock;
        $this-&gt;Telnet();
    }
    
    function &amp;_createSocket() {
        return $this-&gt;_mock;
    }
}</strong>
</pre>
                Ici j'ai déplacé la fantaisie dans le constructeur,
                mais un setter aurait fonctionné tout aussi bien.
                Notez bien que la fantaisie est placée dans une variable
                d'objet avant que le constructeur ne soit attaché.
                C'est nécessaire dans le cas où le constructeur appelle 
                <span class="new_code">connect()</span>.
                Autrement il pourrait donner un valeur nulle à partir de
                <span class="new_code">_createSocket()</span>.
            </p>
            <p>
                Après la réalisation de tout ce travail supplémentaire
                le scénario de test est assez simple.
                Nous avons juste besoin de tester notre nouvelle classe à la place...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($socket);
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                Cette nouvelle classe est très simple bien sûr.
                Elle ne fait qu'initier une valeur renvoyée, à la manière
                d'une fantaisie. Ce serait pas mal non plus si elle pouvait
                vérifier les paramètres entrants.
                Exactement comme un objet fantaisie.
                Il se pourrait bien que nous ayons à réaliser cette astuce régulièrement :
                serait-il possible d'automatiser la création de cette sous-classe ?
            </p>
        
        <p><a class="target" name="partiel"><h2>Un objet fantaisie partiel</h2></a></p>
            <p>
                Bien sûr la réponse est "oui"
                ou alors j'aurais arrêté d'écrire depuis quelques temps déjà !
                Le test précédent a représenté beaucoup de travail,
                mais nous pouvons générer la sous-classe en utilisant
                une approche à celle des objets fantaisie.
            </p>
            <p>
                Voici donc une version avec objet fantaisie partiel du test...
<pre>
<strong>Mock::generatePartial(
        'Telnet',
        'TelnetTestVersion',
        array('_createSocket'));</strong>

class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($this);
        $telnet-&gt;setReturnReference('_createSocket', $socket);
        $telnet-&gt;Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                La fantaisie partielle est une sous-classe de l'original
                dont on aurait "remplacé" les méthodes sélectionnées
                avec des versions de test. L'appel à <span class="new_code">generatePartial()</span>
                nécessite trois paramètres : la classe à sous classer,
                le nom de la nouvelle classe et une liste des méthodes à simuler.
            </p>
            <p>
                Instancier les objets qui en résultent est plutôt délicat.
                L'unique paramètre du constructeur d'un objet fantaisie partiel
                est la référence du testeur unitaire.
                Comme avec les objets fantaisie classiques c'est nécessaire
                pour l'envoi des résultats de test en réponse à la vérification des attentes.
            </p>
            <p>
                Une nouvelle fois le constructeur original n'est pas lancé.
                Indispensable dans le cas où le constructeur aurait besoin
                des méthodes fantaisie : elles n'ont pas encore été initiées !
                Nous initions les valeurs retournées à cet instant et
                ensuite lançons le constructeur avec ses paramètres normaux.
                Cette construction en trois étapes de "new",
                suivie par la mise en place des méthodes et ensuite
                par la lancement du constructeur proprement dit est
                ce qui distingue le code d'un objet fantaisie partiel.
            </p>
            <p>
                A part pour leur construction, toutes ces méthodes
                fantaisie ont les mêmes fonctionnalités que dans
                le cas des objets fantaisie et toutes les méthodes
                non fantaisie se comportent comme avant.
                Nous pouvons mettre en place des attentes très facilement...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($this);
        $telnet-&gt;setReturnReference('_createSocket', $socket);<strong>
        $telnet-&gt;expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
        $telnet-&gt;Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...<strong>
        $telnet-&gt;tally();</strong>
    }
}
</pre>
            </p>
        
        <p><a class="target" name="moins"><h2>Tester moins qu'une classe</h2></a></p>
            <p>
                Les méthodes issues d'un objet fantaisie n'ont pas
                besoin d'être des méthodes fabrique, Il peut s'agir
                de n'importe quelle sorte de méthode.
                Ainsi les objets fantaisie partiels nous permettent
                de prendre le contrôle de n'importe quelle partie d'une classe,
                le constructeur excepté. Nous pourrions même aller jusqu'à
                créer des fantaisies sur toutes les méthodes à part celle
                que nous voulons effectivement tester.
            </p>
            <p>
                Cette situation est assez hypothétique, étant donné
                que je ne l'ai jamais essayée. Je suis ouvert à cette possibilité,
                mais je crains qu'en forçant la granularité d'un objet
                on n'obtienne pas forcément un code de meilleur qualité.
                Personnellement j'utilise les objets fantaisie partiels
                comme moyen de passer outre la création ou alors
                de temps en temps pour tester le modèle de conception TemplateMethod.
            </p>
            <p>
                Pour choisir le mécanisme à utiliser, on en revient
                toujours aux standards de code de votre projet.
            </p>
        
    </div>
        References and related information...
        <ul>
<li>
            La page du projet SimpleTest sur
            <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
        </li>
<li>
            <a href="http://simpletest.org/api/">L'API complète pour SimpleTest</a>
            à partir de PHPDoc.
        </li>
<li>
            La méthode fabrique protégée est décrite dans
            <a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">
            cet article d'IBM</a>. Il s'agit de l'unique papier
            formel que j'ai vu sur ce problème.
        </li>
</ul>
<div class="menu_back"><div class="menu">
<a href="index.html">SimpleTest</a>
                |
                <a href="overview.html">Overview</a>
                |
                <a href="unit_test_documentation.html">Unit tester</a>
                |
                <a href="group_test_documentation.html">Group tests</a>
                |
                <a href="mock_objects_documentation.html">Mock objects</a>
                |
                <a href="partial_mocks_documentation.html">Partial mocks</a>
                |
                <a href="reporter_documentation.html">Reporting</a>
                |
                <a href="expectation_documentation.html">Expectations</a>
                |
                <a href="web_tester_documentation.html">Web tester</a>
                |
                <a href="form_testing_documentation.html">Testing forms</a>
                |
                <a href="authentication_documentation.html">Authentication</a>
                |
                <a href="browser_documentation.html">Scriptable browser</a>
</div></div>
<div class="copyright">
            Copyright<br>Marcus Baker 2006
        </div>
</body>
</html>