File: partial_mocks_documentation.xml

package info (click to toggle)
postfixadmin 2.3.5-2%2Bdeb7u1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 6,200 kB
  • sloc: php: 25,767; xml: 14,485; perl: 964; sh: 664; python: 169; makefile: 84
file content (306 lines) | stat: -rw-r--r-- 15,132 bytes parent folder | download | duplicates (2)
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
<?xml version="1.0" encoding="ISO-8859-1" ?>
<page title="Documentation sur les objets fantaisie partiels" here="Les objets fantaisie partiels">
    <long_title>Documentation SimpleTest : les objets fantaisie partiels</long_title>
    <content>
        <introduction>
            <p>
                Un objet fantaisie partiel n'est ni plus ni moins qu'un modle de conception pour soulager un problme spcifique du test avec des objets fantaisie, celui de placer des objets fantaisie dans des coins serrs. Il s'agit d'un outil assez limit et peut-tre mme une ide pas si bonne que a. Elle est incluse dans SimpleTest pour la simple raison que je l'ai trouve utile  plus d'une occasion et qu'elle m'a pargne pas mal de travail dans ces moments-l.
            </p>
        </introduction>
        <section name="injection" title="Le problme de l'injection dans un objet fantaisie">
            <p>
                Quand un objet en utilise un autre il est trs simple d'y faire circuler une version fantaisie dj prte avec ses attentes. Les choses deviennent un peu plus dlicates si un objet en cre un autre et que le crateur 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 crer un objet fantaisie plutt qu'un &quot;vrai&quot; objet. La classe teste ne sait mme 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 crer une socket rseau pour envoyer ses messages. La mthode de connexion pourrait ressemble  quelque chose comme...
<php><![CDATA[
<strong><?php
    require_once('socket.php');
    
    class Telnet {
        ...
        function &connect($ip, $port, $username, $password) {
            $socket = &new Socket($ip, $port);
            $socket->read( ... );
            ...
        }
    }
?></strong>
]]></php>
                Nous voudrions vraiment avoir une version fantaisie de l'objet socket, que pouvons nous faire ?
            </p>
            <p>
                La premire solution est de passer la socket en tant que paramtre, ce qui force la cration au niveau infrieur. Charger le client de cette tche est effectivement une bonne approche si c'est possible et devrait conduire  un remaniement -- de la cration  partir de l'action. En fait, c'est l une des manires avec lesquels tester en s'appuyant sur des objets fantaisie vous force  coder des solutions plus resserres sur leur objectif. Ils amliorent votre programmation.
            </p>
            <p>
                Voici ce que a devrait tre...
<php><![CDATA[
<?php
    require_once('socket.php');
    
    class Telnet {
        ...
        <strong>function &connect(&$socket, $username, $password) {
            $socket->read( ... );
            ...
        }</strong>
    }
?>
]]></php>
                Sous-entendu, votre code de test est typique d'un cas de test avec un objet fantaisie.
<php><![CDATA[
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &new MockSocket($this);
        ...
        $telnet = &new Telnet();
        $telnet->connect($socket, 'Me', 'Secret');
        ...</strong>
    }
}
]]></php>
                C'est assez vident que vous ne pouvez descendre que d'un niveau. Vous ne voudriez pas que votre application de haut niveau cre tous les fichiers de bas niveau, sockets et autres connexions  la base de donnes dont elle aurait besoin. Elle ne connatrait pas les paramtres du constructeur de toute faon.
            </p>
            <p>
                La solution suivante est de passer l'objet cr sous la forme d'un paramtre optionnel...
<php><![CDATA[
<?php
    require_once('socket.php');
    
    class Telnet {
        ...<strong>
        function &connect($ip, $port, $username, $password, $socket = false) {
            if (!$socket) {
                $socket = &new Socket($ip, $port);
            }
            $socket->read( ... );</strong>
            ...
            return $socket;
        }
    }
?>
]]></php>
                Pour une solution rapide, c'est gnralement suffisant. Ensuite le test est trs similaire : comme si le paramtre tait transmis formellement...
<php><![CDATA[
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &new MockSocket($this);
        ...
        $telnet = &new Telnet();
        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket);
        ...</strong>
    }
}
]]></php>
                Le problme de cette approche tient dans son manque de nettet. Il y a du code de test dans la classe principale et aussi des paramtres transmis dans le scnario de test qui ne sont jamais utiliss. 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 cration...
<php><![CDATA[
<?php
    require_once('socket.php');
    
    class Telnet {<strong>
        function Telnet(&$network) {
            $this->_network = &$network;
        }</strong>
        ...
        function &connect($ip, $port, $username, $password) {<strong>
            $socket = &$this->_network->createSocket($ip, $port);
            $socket->read( ... );</strong>
            ...
            return $socket;
        }
    }
?>
]]></php>
                Il s'agit l probablement de la rponse la plus travaille tant donn que la cration est maintenant situe dans une petite classe spcialise. La fabrique rseau peut tre teste sparment et utilise en tant que fantaisie quand nous testons la classe telnet...
<php><![CDATA[
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &new MockSocket($this);
        ...
        $network = &new MockNetwork($this);
        $network->setReturnReference('createSocket', $socket);
        $telnet = &new Telnet($network);
        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
]]></php>
                Le problme reste que nous ajoutons beaucoup de classes  la bibliothque. 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>
        </section>
        <section name="creation" title="Mthode fabrique protge">
            <p>
                Il existe une technique pour palier  ce problme sans crer de nouvelle classe dans l'application; par contre elle induit la cration d'une sous-classe au moment du test. Premirement nous dplaons la cration de la socket dans sa propre mthode...
<php><![CDATA[
<?php
    require_once('socket.php');
    
    class Telnet {
        ...
        function &connect($ip, $port, $username, $password) {<strong>
            $socket = &$this->_createSocket($ip, $port);</strong>
            $socket->read( ... );
            ...
        }<strong>
        
        function &_createSocket($ip, $port) {
            return new Socket($ip, $port);
        }</strong>
    }
?>
]]></php>
                Il s'agit l de la seule modification dans le code de l'application.
            </p>
            <p>
                Pour le scnario de test, nous devons crer une sous-classe de manire  intercepter la cration de la socket...
<php><![CDATA[
<strong>class TelnetTestVersion extends Telnet {
    var $_mock;
    
    function TelnetTestVersion(&$mock) {
        $this->_mock = &$mock;
        $this->Telnet();
    }
    
    function &_createSocket() {
        return $this->_mock;
    }
}</strong>
]]></php>
                Ici j'ai dplac la fantaisie dans le constructeur, mais un setter aurait fonctionn tout aussi bien. Notez bien que la fantaisie est place dans une variable d'objet avant que le constructeur ne soit attach. C'est ncessaire dans le cas o le constructeur appelle  <code>connect()</code>. Autrement il pourrait donner un valeur nulle  partir de <code>_createSocket()</code>.
            </p>
            <p>
                Aprs la ralisation de tout ce travail supplmentaire le scnario de test est assez simple. Nous avons juste besoin de tester notre nouvelle classe  la place...
<php><![CDATA[
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &new MockSocket($this);
        ...
        $telnet = &new TelnetTestVersion($socket);
        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
]]></php>
                Cette nouvelle classe est trs simple bien sr. Elle ne fait qu'initier une valeur renvoye,  la manire d'une fantaisie. Ce serait pas mal non plus si elle pouvait vrifier les paramtres entrants. Exactement comme un objet fantaisie. Il se pourrait bien que nous ayons  raliser cette astuce rgulirement : serait-il possible d'automatiser la cration de cette sous-classe ?
            </p>
        </section>
        <section name="partiel" title="Un objet fantaisie partiel">
            <p>
                Bien sr la rponse est &quot;oui&quot; ou alors j'aurais arrt d'crire depuis quelques temps dj ! Le test prcdent a reprsent beaucoup de travail, mais nous pouvons gnrer la sous-classe en utilisant une approche  celle des objets fantaisie.
            </p>
            <p>
                Voici donc une version avec objet fantaisie partiel du test...
<php><![CDATA[
<strong>Mock::generatePartial(
        'Telnet',
        'TelnetTestVersion',
        array('_createSocket'));</strong>

class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &new MockSocket($this);
        ...
        $telnet = &new TelnetTestVersion($this);
        $telnet->setReturnReference('_createSocket', $socket);
        $telnet->Telnet();
        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
]]></php>
                La fantaisie partielle est une sous-classe de l'original dont on aurait &quot;remplac&quot; les mthodes slectionnes avec des versions de test. L'appel  <code>generatePartial()</code> ncessite trois paramtres : la classe  sous classer, le nom de la nouvelle classe et une liste des mthodes  simuler.
            </p>
            <p>
                Instancier les objets qui en rsultent est plutt dlicat. L'unique paramtre du constructeur d'un objet fantaisie partiel est la rfrence du testeur unitaire. Comme avec les objets fantaisie classiques c'est ncessaire pour l'envoi des rsultats de test en rponse  la vrification des attentes.
            </p>
            <p>
                Une nouvelle fois le constructeur original n'est pas lanc. Indispensable dans le cas o le constructeur aurait besoin des mthodes fantaisie : elles n'ont pas encore t inities ! Nous initions les valeurs retournes  cet instant et ensuite lanons le constructeur avec ses paramtres normaux. Cette construction en trois tapes de &quot;new&quot;, suivie par la mise en place des mthodes 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 mthodes fantaisie ont les mmes fonctionnalits que dans le cas des objets fantaisie et toutes les mthodes non fantaisie se comportent comme avant. Nous pouvons mettre en place des attentes trs facilement...
<php><![CDATA[
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {
        $socket = &new MockSocket($this);
        ...
        $telnet = &new TelnetTestVersion($this);
        $telnet->setReturnReference('_createSocket', $socket);<strong>
        $telnet->expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
        $telnet->Telnet();
        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
        ...<strong>
        $telnet->tally();</strong>
    }
}
]]></php>
            </p>
        </section>
        <section name="moins" title="Tester moins qu'une classe">
            <p>
                Les mthodes issues d'un objet fantaisie n'ont pas besoin d'tre des mthodes fabrique, Il peut s'agir de n'importe quelle sorte de mthode. Ainsi les objets fantaisie partiels nous permettent de prendre le contrle de n'importe quelle partie d'une classe, le constructeur except. Nous pourrions mme aller jusqu' crer des fantaisies sur toutes les mthodes  part celle que nous voulons effectivement tester.
            </p>
            <p>
                Cette situation est assez hypothtique, tant donn que je ne l'ai jamais essaye. Je suis ouvert  cette possibilit, mais je crains qu'en forant la granularit d'un objet on n'obtienne pas forcment un code de meilleur qualit. Personnellement j'utilise les objets fantaisie partiels comme moyen de passer outre la cration ou alors de temps en temps pour tester le modle de conception TemplateMethod.
            </p>
            <p>
                Pour choisir le mcanisme  utiliser, on en revient toujours aux standards de code de votre projet.
            </p>
        </section>
    </content>
    <internal>
        <link>
            <a href="#injection">Le problme de l'injection d'un objet fantaisie</a>.
        </link>
        <link>
            Dplacer la cration vers une mthode <a href="#creation">fabrique protge</a>.
        </link>
        <link>
            <a href="#partiel">L'objet fantaisie partiel</a> gnre une sous-classe.
        </link>
        <link>
            Les objets fantaisie partiels <a href="#moins">testent moins qu'une classe</a>.
        </link>
    </internal>
    <external>
        <link>
            La page du projet SimpleTest sur <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
        </link>
        <link>
            <a href="http://simpletest.sourceforge.net/">L'API complte pour SimpleTest</a>  partir de PHPDoc.
        </link>
        <link>
            La mthode fabrique protge est dcrite 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 problme.
        </link>
    </external>
    <meta>
        <keywords>
            dveloppement logiciel en php,
            dvelopement d'un scnario de test en php,
            programmation php avec base de donnes,
            outils de dveloppement logiciel,
            tutoriel avanc en php,
            scripts  la manire de phpunit,
            architecture,
            ressources php,
            objets fantaisie,
            junit,
            framework de test en php,
            test unitaire,
            test en php
        </keywords>
    </meta>
</page>