source: trunk/server/www/vendors/simpletest/docs/fr/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: 18.3 KB
Line 
1<html>
2<head>
3<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
4<title>Documentation SimpleTest : les objets fantaisie partiels</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                <a href="partial_mocks_documentation.html">Partial mocks</a>
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>Documentation sur les objets fantaisie partiels</h1>
34        This page...
35        <ul>
36<li>
37            <a href="#injection">Le problÚme de l'injection d'un objet fantaisie</a>.
38        </li>
39<li>
40            Déplacer la création vers une méthode <a href="#creation">fabrique protégée</a>.
41        </li>
42<li>
43            <a href="#partiel">L'objet fantaisie partiel</a> génÚre une sous-classe.
44        </li>
45<li>
46            Les objets fantaisie partiels <a href="#moins">testent moins qu'une classe</a>.
47        </li>
48</ul>
49<div class="content">
50       
51            <p>
52                Un objet fantaisie partiel n'est ni plus ni moins
53                qu'un modÚle de conception pour soulager un problÚme spécifique
54                du test avec des objets fantaisie, celui de placer
55                des objets fantaisie dans des coins serrés.
56                Il s'agit d'un outil assez limité et peut-être même
57                une idée pas si bonne que ça. Elle est incluse dans SimpleTest
58                pour la simple raison que je l'ai trouvée utile
59                à plus d'une occasion et qu'elle m'a épargnée
60                pas mal de travail dans ces moments-là.
61            </p>
62       
63        <p><a class="target" name="injection"><h2>Le problÚme de l'injection dans un objet fantaisie</h2></a></p>
64            <p>
65                Quand un objet en utilise un autre il est trÚs simple
66                d'y faire circuler une version fantaisie déjà prête
67                avec ses attentes. Les choses deviennent un peu plus délicates
68                si un objet en crée un autre et que le créateur est celui
69                que l'on souhaite tester. Cela revient à dire que l'objet
70                créé devrait être une fantaisie, mais nous pouvons
71                difficilement dire à notre classe sous test de créer
72                un objet fantaisie plutÃŽt qu'un "vrai" objet.
73                La classe testée ne sait même pas qu'elle travaille dans un environnement de test.
74            </p>
75            <p>
76                Par exemple, supposons que nous sommes en train
77                de construire un client telnet et qu'il a besoin
78                de créer une socket réseau pour envoyer ses messages.
79                La méthode de connexion pourrait ressemble à quelque chose comme...
80<pre>
81<strong>&lt;?php
82require_once('socket.php');
83
84class Telnet {
85    ...
86    function &amp;connect($ip, $port, $username, $password) {
87        $socket = &amp;new Socket($ip, $port);
88        $socket-&gt;read( ... );
89        ...
90    }
91}
92?&gt;</strong>
93</pre>
94                Nous voudrions vraiment avoir une version fantaisie
95                de l'objet socket, que pouvons nous faire ?
96            </p>
97            <p>
98                La premiÚre solution est de passer la socket en
99                tant que paramÚtre, ce qui force la création
100                au niveau inférieur. Charger le client de cette tâche
101                est effectivement une bonne approche si c'est possible
102                et devrait conduire à un remaniement -- de la création
103                à partir de l'action. En fait, c'est là une des maniÚres
104                avec lesquels tester en s'appuyant sur des objets fantaisie
105                vous force à coder des solutions plus resserrées sur leur objectif.
106                Ils améliorent votre programmation.
107            </p>
108            <p>
109                Voici ce que ça devrait être...
110<pre>
111&lt;?php
112require_once('socket.php');
113
114class Telnet {
115    ...
116    <strong>function &amp;connect(&amp;$socket, $username, $password) {
117        $socket-&gt;read( ... );
118        ...
119    }</strong>
120}
121?&gt;
122</pre>
123                Sous-entendu, votre code de test est typique d'un cas
124                de test avec un objet fantaisie.
125<pre>
126class TelnetTest extends UnitTestCase {
127    ...
128    function testConnection() {<strong>
129        $socket = &amp;new MockSocket($this);
130        ...
131        $telnet = &amp;new Telnet();
132        $telnet-&gt;connect($socket, 'Me', 'Secret');
133        ...</strong>
134    }
135}
136</pre>
137                C'est assez évident que vous ne pouvez descendre que d'un niveau.
138                Vous ne voudriez pas que votre application de haut niveau
139                crée tous les fichiers de bas niveau, sockets et autres connexions
140                à la base de données dont elle aurait besoin.
141                Elle ne connaîtrait pas les paramÚtres du constructeur de toute façon.
142            </p>
143            <p>
144                La solution suivante est de passer l'objet créé sous la forme
145                d'un paramÚtre optionnel...
146<pre>
147&lt;?php
148require_once('socket.php');
149
150class Telnet {
151    ...<strong>
152    function &amp;connect($ip, $port, $username, $password, $socket = false) {
153        if (!$socket) {
154            $socket = &amp;new Socket($ip, $port);
155        }
156        $socket-&gt;read( ... );</strong>
157        ...
158        return $socket;
159    }
160}
161?&gt;
162</pre>
163                Pour une solution rapide, c'est généralement suffisant.
164                Ensuite le test est trÚs similaire : comme si le paramÚtre
165                était transmis formellement...
166<pre>
167class TelnetTest extends UnitTestCase {
168    ...
169    function testConnection() {<strong>
170        $socket = &amp;new MockSocket($this);
171        ...
172        $telnet = &amp;new Telnet();
173        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret', &amp;$socket);
174        ...</strong>
175    }
176}
177</pre>
178                Le problÚme de cette approche tient dans son manque de netteté.
179                Il y a du code de test dans la classe principale et aussi
180                des paramÚtres transmis dans le scénario de test
181                qui ne sont jamais utilisés. Il s'agit là d'une approche
182                rapide et sale, mais qui ne reste pas moins efficace
183                dans la plupart des situations.
184            </p>
185            <p>
186                Une autre solution encore est de laisser un objet fabrique
187                s'occuper de la création...
188<pre>
189&lt;?php
190require_once('socket.php');
191
192class Telnet {<strong>
193    function Telnet(&amp;$network) {
194        $this-&gt;_network = &amp;$network;
195    }</strong>
196    ...
197    function &amp;connect($ip, $port, $username, $password) {<strong>
198        $socket = &amp;$this-&gt;_network-&gt;createSocket($ip, $port);
199        $socket-&gt;read( ... );</strong>
200        ...
201        return $socket;
202    }
203}
204?&gt;
205</pre>
206                Il s'agit là probablement de la réponse la plus travaillée
207                étant donné que la création est maintenant située
208                dans une petite classe spécialisée. La fabrique réseau
209                peut être testée séparément et utilisée en tant que fantaisie
210                quand nous testons la classe telnet...
211<pre>
212class TelnetTest extends UnitTestCase {
213    ...
214    function testConnection() {<strong>
215        $socket = &amp;new MockSocket($this);
216        ...
217        $network = &amp;new MockNetwork($this);
218        $network-&gt;setReturnReference('createSocket', $socket);
219        $telnet = &amp;new Telnet($network);
220        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
221        ...</strong>
222    }
223}
224</pre>
225                Le problÚme reste que nous ajoutons beaucoup de classes
226                à la bibliothÚque. Et aussi que nous utilisons beaucoup
227                de fabriques ce qui rend notre code un peu moins intuitif.
228                La solution la plus flexible, mais aussi la plus complexe.
229            </p>
230            <p>
231                Peut-on trouver un juste milieu ?
232            </p>
233       
234        <p><a class="target" name="creation"><h2>Méthode fabrique protégée</h2></a></p>
235            <p>
236                Il existe une technique pour palier à ce problÚme
237                sans créer de nouvelle classe dans l'application;
238                par contre elle induit la création d'une sous-classe au moment du test.
239                PremiÚrement nous déplaçons la création de la socket dans sa propre méthode...
240<pre>
241&lt;?php
242require_once('socket.php');
243
244class Telnet {
245    ...
246    function &amp;connect($ip, $port, $username, $password) {<strong>
247        $socket = &amp;$this-&gt;_createSocket($ip, $port);</strong>
248        $socket-&gt;read( ... );
249        ...
250    }<strong>
251   
252    function &amp;_createSocket($ip, $port) {
253        return new Socket($ip, $port);
254    }</strong>
255}
256?&gt;
257</pre>
258                Il s'agit là de la seule modification dans le code de l'application.
259            </p>
260            <p>
261                Pour le scénario de test, nous devons créer
262                une sous-classe de maniÚre à intercepter la création de la socket...
263<pre>
264<strong>class TelnetTestVersion extends Telnet {
265    var $_mock;
266   
267    function TelnetTestVersion(&amp;$mock) {
268        $this-&gt;_mock = &amp;$mock;
269        $this-&gt;Telnet();
270    }
271   
272    function &amp;_createSocket() {
273        return $this-&gt;_mock;
274    }
275}</strong>
276</pre>
277                Ici j'ai déplacé la fantaisie dans le constructeur,
278                mais un setter aurait fonctionné tout aussi bien.
279                Notez bien que la fantaisie est placée dans une variable
280                d'objet avant que le constructeur ne soit attaché.
281                C'est nécessaire dans le cas où le constructeur appelle
282                <span class="new_code">connect()</span>.
283                Autrement il pourrait donner un valeur nulle à partir de
284                <span class="new_code">_createSocket()</span>.
285            </p>
286            <p>
287                AprÚs la réalisation de tout ce travail supplémentaire
288                le scénario de test est assez simple.
289                Nous avons juste besoin de tester notre nouvelle classe à la place...
290<pre>
291class TelnetTest extends UnitTestCase {
292    ...
293    function testConnection() {<strong>
294        $socket = &amp;new MockSocket($this);
295        ...
296        $telnet = &amp;new TelnetTestVersion($socket);
297        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
298        ...</strong>
299    }
300}
301</pre>
302                Cette nouvelle classe est trÚs simple bien sûr.
303                Elle ne fait qu'initier une valeur renvoyée, à la maniÚre
304                d'une fantaisie. Ce serait pas mal non plus si elle pouvait
305                vérifier les paramÚtres entrants.
306                Exactement comme un objet fantaisie.
307                Il se pourrait bien que nous ayons à réaliser cette astuce réguliÚrement :
308                serait-il possible d'automatiser la création de cette sous-classe ?
309            </p>
310       
311        <p><a class="target" name="partiel"><h2>Un objet fantaisie partiel</h2></a></p>
312            <p>
313                Bien sûr la réponse est "oui"
314                ou alors j'aurais arrêté d'écrire depuis quelques temps déjà !
315                Le test précédent a représenté beaucoup de travail,
316                mais nous pouvons générer la sous-classe en utilisant
317                une approche à celle des objets fantaisie.
318            </p>
319            <p>
320                Voici donc une version avec objet fantaisie partiel du test...
321<pre>
322<strong>Mock::generatePartial(
323        'Telnet',
324        'TelnetTestVersion',
325        array('_createSocket'));</strong>
326
327class TelnetTest extends UnitTestCase {
328    ...
329    function testConnection() {<strong>
330        $socket = &amp;new MockSocket($this);
331        ...
332        $telnet = &amp;new TelnetTestVersion($this);
333        $telnet-&gt;setReturnReference('_createSocket', $socket);
334        $telnet-&gt;Telnet();
335        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
336        ...</strong>
337    }
338}
339</pre>
340                La fantaisie partielle est une sous-classe de l'original
341                dont on aurait "remplacé" les méthodes sélectionnées
342                avec des versions de test. L'appel à <span class="new_code">generatePartial()</span>
343                nécessite trois paramÚtres : la classe à sous classer,
344                le nom de la nouvelle classe et une liste des méthodes à simuler.
345            </p>
346            <p>
347                Instancier les objets qui en résultent est plutÃŽt délicat.
348                L'unique paramÚtre du constructeur d'un objet fantaisie partiel
349                est la référence du testeur unitaire.
350                Comme avec les objets fantaisie classiques c'est nécessaire
351                pour l'envoi des résultats de test en réponse à la vérification des attentes.
352            </p>
353            <p>
354                Une nouvelle fois le constructeur original n'est pas lancé.
355                Indispensable dans le cas où le constructeur aurait besoin
356                des méthodes fantaisie : elles n'ont pas encore été initiées !
357                Nous initions les valeurs retournées à cet instant et
358                ensuite lançons le constructeur avec ses paramÚtres normaux.
359                Cette construction en trois étapes de "new",
360                suivie par la mise en place des méthodes et ensuite
361                par la lancement du constructeur proprement dit est
362                ce qui distingue le code d'un objet fantaisie partiel.
363            </p>
364            <p>
365                A part pour leur construction, toutes ces méthodes
366                fantaisie ont les mêmes fonctionnalités que dans
367                le cas des objets fantaisie et toutes les méthodes
368                non fantaisie se comportent comme avant.
369                Nous pouvons mettre en place des attentes trÚs facilement...
370<pre>
371class TelnetTest extends UnitTestCase {
372    ...
373    function testConnection() {
374        $socket = &amp;new MockSocket($this);
375        ...
376        $telnet = &amp;new TelnetTestVersion($this);
377        $telnet-&gt;setReturnReference('_createSocket', $socket);<strong>
378        $telnet-&gt;expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
379        $telnet-&gt;Telnet();
380        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
381        ...<strong>
382        $telnet-&gt;tally();</strong>
383    }
384}
385</pre>
386            </p>
387       
388        <p><a class="target" name="moins"><h2>Tester moins qu'une classe</h2></a></p>
389            <p>
390                Les méthodes issues d'un objet fantaisie n'ont pas
391                besoin d'être des méthodes fabrique, Il peut s'agir
392                de n'importe quelle sorte de méthode.
393                Ainsi les objets fantaisie partiels nous permettent
394                de prendre le contrÃŽle de n'importe quelle partie d'une classe,
395                le constructeur excepté. Nous pourrions même aller jusqu'à
396                créer des fantaisies sur toutes les méthodes à part celle
397                que nous voulons effectivement tester.
398            </p>
399            <p>
400                Cette situation est assez hypothétique, étant donné
401                que je ne l'ai jamais essayée. Je suis ouvert à cette possibilité,
402                mais je crains qu'en forçant la granularité d'un objet
403                on n'obtienne pas forcément un code de meilleur qualité.
404                Personnellement j'utilise les objets fantaisie partiels
405                comme moyen de passer outre la création ou alors
406                de temps en temps pour tester le modÚle de conception TemplateMethod.
407            </p>
408            <p>
409                Pour choisir le mécanisme à utiliser, on en revient
410                toujours aux standards de code de votre projet.
411            </p>
412       
413    </div>
414        References and related information...
415        <ul>
416<li>
417            La page du projet SimpleTest sur
418            <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
419        </li>
420<li>
421            <a href="http://simpletest.org/api/">L'API complÚte pour SimpleTest</a>
422            à partir de PHPDoc.
423        </li>
424<li>
425            La méthode fabrique protégée est décrite dans
426            <a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">
427            cet article d'IBM</a>. Il s'agit de l'unique papier
428            formel que j'ai vu sur ce problÚme.
429        </li>
430</ul>
431<div class="menu_back"><div class="menu">
432<a href="index.html">SimpleTest</a>
433                |
434                <a href="overview.html">Overview</a>
435                |
436                <a href="unit_test_documentation.html">Unit tester</a>
437                |
438                <a href="group_test_documentation.html">Group tests</a>
439                |
440                <a href="mock_objects_documentation.html">Mock objects</a>
441                |
442                <a href="partial_mocks_documentation.html">Partial mocks</a>
443                |
444                <a href="reporter_documentation.html">Reporting</a>
445                |
446                <a href="expectation_documentation.html">Expectations</a>
447                |
448                <a href="web_tester_documentation.html">Web tester</a>
449                |
450                <a href="form_testing_documentation.html">Testing forms</a>
451                |
452                <a href="authentication_documentation.html">Authentication</a>
453                |
454                <a href="browser_documentation.html">Scriptable browser</a>
455</div></div>
456<div class="copyright">
457            Copyright<br>Marcus Baker 2006
458        </div>
459</body>
460</html>
Note: See TracBrowser for help on using the repository browser.