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
|
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Documentation SimpleTest : les bouchons serveur</title>
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
</head>
<body>
<div class="menu_back">
<div class="menu">
<h2>
<a href="index.html">SimpleTest</a>
</h2>
<ul>
<li>
<a href="overview.html">Overview</a>
</li>
<li>
<a href="unit_test_documentation.html">Unit tester</a>
</li>
<li>
<a href="group_test_documentation.html">Group tests</a>
</li>
<li>
<a href="server_stubs_documentation.html">Server stubs</a>
</li>
<li>
<a href="mock_objects_documentation.html">Mock objects</a>
</li>
<li>
<a href="partial_mocks_documentation.html">Partial mocks</a>
</li>
<li>
<a href="reporter_documentation.html">Reporting</a>
</li>
<li>
<a href="expectation_documentation.html">Expectations</a>
</li>
<li>
<a href="web_tester_documentation.html">Web tester</a>
</li>
<li>
<a href="form_testing_documentation.html">Testing forms</a>
</li>
<li>
<a href="authentication_documentation.html">Authentication</a>
</li>
<li>
<a href="browser_documentation.html">Scriptable browser</a>
</li>
</ul>
</div>
</div>
<h1>Documentation sur les bouchons serveur</h1>
<div class="content">
<p>
<a class="target" name="quoi">
<h2>Que sont les bouchons serveur ?</h2>
</a>
</p>
<p>
Au départ il s'agit d'un modèle de conception initié par Robert Binder (Testing object-oriented systems: models, patterns, and tools, Addison-Wesley) in 1999. Un bouchon serveur est une simulation d'un objet ou d'un composant. Il doit remplacer exactement un composant dans un système pour des raisons de testabilité ou de prototypage, tout en restant léger. Il permet aux tests de tourner plus rapidement ou alors, si la classe simulée n'a pas été écrite, juste de fonctionner.
</p>
<p>
<a class="target" name="creation">
<h2>Créer des bouchons serveur</h2>
</a>
</p>
<p>
Nous avons juste besoin d'une classe préexistante, par exemple une connexion vers une base de données qui ressemblerait à...
<pre>
<strong>class DatabaseConnection {
function DatabaseConnection() {
}
function query() {
}
function selectQuery() {
}
}</strong>
</pre>
La classe n'a même pas encore besoin d'avoir été implémentée. Pour créer la version bouchonnée de cette classe, nous incluons la librairie de bouchon serveur et exécutons le générateur...
<pre>
<strong>require_once('simpletest/mock_objects.php');
require_once('database_connection.php');
Stub::generate('DatabaseConnection');</strong>
</pre>
Est généré un clone de la classe appelé <span class="new_code">StubDatabaseConnection</span>. Nous pouvons alors créer des instances de cette nouvelle classe à l'intérieur de notre prototype de script...
<pre>
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');
Stub::generate('DatabaseConnection');
<strong>
$connection = new StubDatabaseConnection();
</strong>
</pre>
La version bouchonnée de la classe contient toutes les méthodes de l'original de telle sorte qu'une opération comme <span class="new_code">$connection->query()</span> soit encore légale. La valeur retournée sera <span class="new_code">null</span>, Mais nous pouvons y remédier avec...
<pre>
<strong>$connection->setReturnValue('query', 37)</strong>
</pre>
Désormais à chaque appel de <span class="new_code">$connection->query()</span> nous obtenons un résultat de 37. Nous pouvons choisir n'importe quelle valeur pour le résultat, par exemple un hash de résultats provenant d'un base de données imaginaire ou alors une liste d'objets persistants. Peu importe les paramètres, nous obtenons systématiquement les même valeurs chaque fois qu'ils ont été initialisés de la sorte : ça ne ressemble peut-être pas à une réponse convaincante venant d'une connexion vers une base de données. Mais pour la demi-douzaine de lignes d'une méthode de test c'est souvent largement suffisant.
</p>
<p>
<a class="target" name="mod%C3%A8les">
<h2>Modèles de simulation</h2>
</a>
</p>
<p>
Sauf que les choses ne sont que rarement aussi simples. Parmi les problèmes les plus courants on trouve les itérateurs : le renvoi d'une valeur constante peut causer une boucle infini dans l'objet testé. Pour ceux-ci nous avons besoin de mettre sur pied une suite de valeurs. Prenons par exemple un itérateur simple qui ressemble à...
<pre>
class Iterator {
function Iterator() {
}
function next() {
}
}
</pre>
C'est probablement le plus simple des itérateurs possibles. Supposons que cet itérateur ne retourne que du texte, jusqu'à la fin - quand il retourne <span class="new_code">false</span>. Une simulation est possible avec...
<pre>
<strong>Stub::generate('Iterator');
$iterator = new StubIterator();
$iterator->setReturnValue('next', false);
$iterator->setReturnValueAt(0, 'next', 'First string');
$iterator->setReturnValueAt(1, 'next', 'Second string');</strong>
</pre>
A l'appel de <span class="new_code">next()</span> sur l'itérateur bouchonné il va d'abord renvoyer "First string", puis au second appel c'est "Second string" qui sera renvoyé. Finalement pour tous les autres appels, il s'agira d'un <span class="new_code">false</span>. Les valeurs renvoyées successivement ont priorité sur la valeur constante renvoyé. Cette dernière est un genre de valeur par défaut.
</p>
<p>
Une autre situation délicate est une opération <span class="new_code">get()</span> surchargée. Un exemple ? Un porteur d'information avec des pairs de clef / valeur. Prenons une classe de configuration...
<pre>
class Configuration {
function Configuration() {
}
function getValue($key) {
}
}
</pre>
Il s'agit d'une situation propice à l'utilisation d'objets bouchon, surtout que la configuration en production dépend invariablement de la machine : l'utiliser directement ne va pas nous aider à maintenir notre confiance dans nos tests. Sauf que le problème tient de ce que toutes les données proviennent de la méthode <span class="new_code">getValue()</span> et que nous voulons des résultats différents suivant la clef. Par chance les bouchons ont un système de filtre...
<pre>
<strong>Stub::generate('Configuration');
$config = &new StubConfiguration();
$config->setReturnValue('getValue', 'primary', array('db_host'));
$config->setReturnValue('getValue', 'admin', array('db_user'));
$config->setReturnValue('getValue', 'secret', array('db_password'));</strong>
</pre>
Ce paramètre supplémentaire est une liste d'arguments que l'on peut utiliser. Dans ce cas nous essayons d'utiliser un unique argument, à savoir la clef recherchée. Maintenant quand on invoque le bouchon serveur via la méthode <span class="new_code">getValue()</span> avec...
<pre>
$config->getValue('db_user');
</pre>
...il renvoie "admin". Il le trouve en essayant d'assortir successivement les arguments d'entrée avec sa liste de ceux de sortie jusqu'au moment où une correspondance exacte est trouvée.
</p>
<p>
Vous pouvez définir un argument par défaut avec...
<pre>
<strong>
$config->setReturnValue('getValue', false, array('*'));</strong>
</pre>
Attention ce n'est pas équivalent à initialiser la valeur retournée sans aucun argument.
<pre>
<strong>
$config->setReturnValue('getValue', false);</strong>
</pre>
Dans le premier cas il acceptera n'importe quel argument, mais exactement un -- pas plus -- est nécessaire. Dans le second cas n'importe quel nombre d'arguments fera l'affaire : il agit comme un <cite>catchall</cite> après tous les correspondances. Prenez garde à l'ordre : si nous ajoutons un autre paramètre après le joker ('*') il sera ignoré puisque le joker aura été trouvé auparavant. Avec des listes de paramètres complexes l'ordre peut devenir crucial, au risque de perdre des correspondances souhaitées, masquées par un joker antérieur. Pensez à mettre d'abord les cas les plus spécifiques si vous n'êtes pas sûr.
</p>
<p>
Il y a des fois où l'on souhaite qu'un objet spécifique soit servi par le bouchon plutôt qu'une simple copie. La sémantique de la copie en PHP nous force à utiliser une autre méthode pour cela. Vous êtes peut-être en train de simuler un conteneur par exemple...
<pre>
class Thing {
}
class Vector {
function Vector() {
}
function get($index) {
}
}
</pre>
Dans ce cas vous pouvez mettre une référence dans la liste renvoyée par le bouchon...
<pre>
Stub::generate('Vector');
$thing = new Thing();<strong>
$vector = &new StubVector();
$vector->setReturnReference('get', $thing, array(12));</strong>
</pre>
Avec ce petit arrangement vous vous assurez qu'à chaque fois que <span class="new_code">$vector->get(12)</span> est appelé il renverra le même <span class="new_code">$thing</span>.
</p>
<p>
Ces trois facteurs, ordre, paramètres et copie (ou référence), peuvent être combinés orthogonalement. Par exemple...
<pre>
$complex = &new StubComplexThing();
$stuff = new Stuff();<strong>
$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1));</strong>
</pre>
Le <span class="new_code">$stuff</span> ne sera renvoyé qu'au troisième appel et seulement si deux paramètres étaient indiqués, avec la contrainte que le second de ceux-ci soit l'entier 1. N'est-ce pas suffisant pour des situations de prototypage simple ?
</p>
<p>
Un dernier cas critique reste celle d'un objet en créant un autre, connu sous le nom du modèle factory - fabrique. Supposons qu'après une requête réussie à notre base de données imaginaire, un ensemble de résultats est retourné sous la forme d'un itérateur, chaque appel à <span class="new_code">next()</span> donnant un ligne et à la fin un <span class="new_code">false</span>.
Au premier abord, ça donne l'impression d'être cauchemardesque à simuler. Alors qu'en fait tout peut être bouchonné en utilisant les mécanismes ci-dessus.
</p>
<p>
Voici comment...
<pre>
Stub::generate('DatabaseConnection');
Stub::generate('ResultIterator');
class DatabaseTest extends UnitTestCase {
function testUserFinder() {<strong>
$result = &new StubResultIterator();
$result->setReturnValue('next', false);
$result->setReturnValueAt(0, 'next', array(1, 'tom'));
$result->setReturnValueAt(1, 'next', array(3, 'dick'));
$result->setReturnValueAt(2, 'next', array(6, 'harry'));
$connection = &new StubDatabaseConnection();
$connection->setReturnValue('query', false);
$connection->setReturnReference(
'query',
$result,
array('select id, name from users'));</strong>
$finder = &new UserFinder($connection);
$this->assertIdentical(
$finder->findNames(),
array('tom', 'dick', 'harry'));
}
}
</pre>
Désormais ce n'est que si notre <span class="new_code">$connection</span> est appelé avec la bonne <span class="new_code">query()</span> que le <span class="new_code">$result</span> sera renvoyé après le troisième appel à <span class="new_code">next()</span>. Cela devrait être suffisant pour que notre classe <span class="new_code">UserFinder</span>, la classe effectivement testée à ce niveau, puisse s'exécuter comme il faut. Un test très précis et pas une seule base de données à l'horizon.
</p>
<p>
<a class="target" name="options">
<h2>Options de création pour les bouchons</h2>
</a>
</p>
<p>
Il y a d'autres options additionnelles à la création d'un bouchon. Au moment de la génération nous pouvons changer le nom de la classe...
<pre>
<strong>Stub::generate('Iterator', 'MyStubIterator');
$iterator = &new MyStubIterator();
</strong>
</pre>
Pris tout seul ce n'est pas très utile étant donné qu'il n'y aurait pas de différence entre cette classe et celle par défaut -- à part le nom bien entendu. Par contre nous pouvons aussi lui ajouter d'autres méthodes qui ne se trouveraient pas dans l'interface originale...
<pre>
class Iterator {
}
<strong>Stub::generate('Iterator', 'PrototypeIterator', array('next', 'isError'));
$iterator = &new PrototypeIterator();
$iterator->setReturnValue('next', 0);
</strong>
</pre>
Les méthodes <span class="new_code">next()</span> et <span class="new_code">isError()</span> peuvent maintenant renvoyer des ensembles de valeurs exactement comme si elles existaient dans la classe originale.
</p>
<p>
Un moyen encore plus ésotérique de modifier les bouchons est de changer le joker utiliser par défaut pour la correspondance des paramètres.
<pre>
<strong>Stub::generate('Connection');
$iterator = &new StubConnection('wild');
$iterator->setReturnValue('query', array('id' => 33), array('wild'));
</strong>
</pre>
L'unique raison valable pour effectuer cette opération, c'est quand vous souhaitez tester la chaîne "*" sans pour autant l'interpréter comme un "n'importe lequel".
</p>
</div>
<div class="copyright">
Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004
</div>
</body>
</html>
|