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
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
|
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
Prise en main rapide de SimpleTest pour PHP -
Tests unitaire et objets fantaisie pour PHP
</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>Prise en main rapide de SimpleTest</h1>
This page...
<ul>
<li>
<a href="#unit">Utiliser le testeur rapidement</a>
avec un exemple.
</li>
<li>
<a href="#group">Groupes de tests</a>
pour tester en un seul clic.
</li>
<li>
<a href="#mock">Utiliser les objets fantaisie</a>
pour faciliter les tests et gagner en contrôle.
</li>
<li>
<a href="#web">Tester des pages web</a>
au niveau de l'HTML.
</li>
</ul>
<div class="content">
<p>
Le présent article présuppose que vous soyez familier avec
le concept de tests unitaires ainsi que celui de développement
web avec le langage PHP. Il s'agit d'un guide pour le nouvel
et impatient utilisateur de
<a href="https://sourceforge.net/project/showfiles.php?group_id=76550">SimpleTest</a>.
Pour une documentation plus complète, particulièrement si
vous découvrez les tests unitaires, consultez la
<a href="http://www.lastcraft.com/unit_test_documentation.php">documentation
en cours</a>, et pour des exemples de scénarios de test,
consultez le
<a href="http://www.lastcraft.com/first_test_tutorial.php">tutorial
sur les tests unitaires</a>.
</p>
<p><a class="target" name="unit"><h2>Utiliser le testeur rapidement</h2></a></p>
<p>
Parmi les outils de test pour logiciel, le testeur unitaire
est le plus proche du développeur. Dans un contexte de
développement agile, le code de test se place juste à côté
du code source étant donné que tous les deux sont écrits
simultanément. Dans ce contexte, SimpleTest aspire à être
une solution complète de test pour un développeur PHP et
s'appelle "Simple" parce qu'elle devrait être simple à
utiliser et à étendre. Ce nom n'était pas vraiment un bon
choix. Non seulement cette solution inclut toutes les
fonctions classiques qu'on est en droit d'attendre de la
part des portages de <a href="http://www.junit.org/">JUnit</a> et des <a href="http://sourceforge.net/projects/phpunit/">PHPUnit</a>,
mais elle inclut aussi les <a href="http://www.mockobjects.com/">objets fantaisie ou
"mock objects"</a>.
</p>
<p>
Ce qui rend cet outil immédiatement utile pour un développeur PHP,
c'est son navigateur web interne.
Il permet des tests qui parcourent des sites web, remplissent
des formulaires et testent le contenu des pages.
Etre capable d'écrire ces tests en PHP veut dire qu'il devient
facile d'écrire des tests de recette (ou d'intégration).
Un exemple serait de confirmer qu'un utilisateur a bien été ajouté
dans une base de données après s'être enregistré sur une site web.
</p>
<p>
La démonstration la plus rapide : l'exemple
</p>
<p>
Supposons que nous sommes en train de tester une simple
classe de log dans un fichier : elle s'appelle
<span class="new_code">Log</span> dans <em>classes/Log.php</em>. Commençons
par créer un script de test, appelé
<em>tests/log_test.php</em>. Son contenu est le suivant...
<pre>
<?php
<strong>require_once('simpletest/autorun.php');</strong>
require_once('../classes/log.php');
class TestOfLogging extends <strong>UnitTestCase</strong> {
}
?>
</pre>
Ici le répertoire <em>simpletest</em> est soit dans le
dossier courant, soit dans les dossiers pour fichiers
inclus. Vous auriez à éditer ces arborescences suivant
l'endroit où vous avez installé SimpleTest.
Le fichier "autorun.php" fait plus que juste inclure
les éléments de SimpleTest : il lance aussi les tests pour nous.
</p>
<p>
<span class="new_code">TestOfLogging</span> est notre premier scénario de test
et il est pour l'instant vide.
Chaque scénario de test est une classe qui étend une des classes
de base de SimpleTest. Nous pouvons avoir autant de classes de ce type
que nous voulons.
</p>
<p>
Avec ces trois lignes d'échafaudage
l'inclusion de notre classe <span class="new_code">Log</span>, nous avons une suite
de tests. Mais pas encore de test !
</p>
<p>
Pour notre premier test, supposons que la classe <span class="new_code">Log</span>
prenne le nom du fichier à écrire au sein du constructeur,
et que nous avons un répertoire temporaire dans lequel placer
ce fichier.
<pre>
<?php
require_once('simpletest/autorun.php');
require_once('../classes/log.php');
class TestOfLogging extends UnitTestCase {
function <strong>testLogCreatesNewFileOnFirstMessage()</strong> {
@unlink('/temp/test.log');
$log = new Log('/temp/test.log');
<strong>$this->assertFalse(file_exists('/temp/test.log'));</strong>
$log->message('Should write this to a file');
<strong>$this->assertTrue(file_exists('/temp/test.log'));</strong>
}
}
?>
</pre>
Au lancement du scénario de test, toutes les méthodes qui
commencent avec la chaîne <span class="new_code">test</span> sont
identifiées puis exécutées.
Si la méthode commence par <span class="new_code">test</span>, c'est un test.
Remarquez bien le nom très long de notre exemple :
<span class="new_code">testLogCreatesNewFileOnFirstMessage()</span>.
C'est bel et bien délibéré : ce style est considéré désirable
et il rend la sortie du test plus lisible.
</p>
<p>
D'ordinaire nous avons bien plusieurs méthodes de tests.
Mais ce sera pour plus tard.
</p>
<p>
Les assertions dans les
méthodes de test envoient des messages vers le framework de
test qui affiche immédiatement le résultat. Cette réponse
immédiate est importante, non seulement lors d'un crash
causé par le code, mais aussi de manière à rapprocher
l'affichage de l'erreur au plus près du scénario de test
concerné via un appel à <span class="new_code">print</span>code>.
</p>
<p>
Pour voir ces résultats lançons effectivement les tests.
Aucun autre code n'est nécessaire, il suffit d'ouvrir
la page dans un navigateur.
</p>
<p>
En cas échec, l'affichage ressemble à...
<div class="demo">
<h1>TestOfLogging</h1>
<span class="fail">Fail</span>: testcreatingnewfile->True assertion failed.<br>
<div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
<strong>1</strong> passes and <strong>1</strong> fails.</div>
</div>
...et si ça passe, on obtient...
<div class="demo">
<h1>TestOfLogging</h1>
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
<strong>2</strong> passes and <strong>0</strong> fails.</div>
</div>
Et si vous obtenez ça...
<div class="demo">
<b>Fatal error</b>: Failed opening required '../classes/log.php' (include_path='') in <b>/home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php</b> on line <b>7</b>
</div>
c'est qu'il vous manque le fichier <em>classes/Log.php</em>
qui pourrait ressembler à :
<pre>
<?php<strong>
class Log {
function Log($file_path) {
}
function message() {
}
}</strong>
?>
</pre>
C'est largement plus sympathique d'écrire le code après le test.
Plus que sympatique même - cette technique s'appelle
"Développement Piloté par les Tests" ou
"Test Driven Development" en anglais.
</p>
<p>
Pour plus de renseignements sur le testeur, voyez la
<a href="unit_test_documentation.html">documentation pour les tests de régression</a>.
</p>
<p><a class="target" name="group"><h2>Construire des groupes de tests</h2></a></p>
<p>
Il est peu probable que dans une véritable application on
ait uniquement besoin de passer un seul scénario de test.
Cela veut dire que nous avons besoin de grouper les
scénarios dans un script de test qui peut, si nécessaire,
lancer tous les tests de l'application.
</p>
<p>
Notre première étape est de créer un nouveau fichier appelé
<em>tests/all_tests.php</em> et d'y inclure le code suivant...
<pre>
<?php
<strong>require_once('simpletest/autorun.php');</strong>
class AllTests extends <strong>TestSuite</strong> {
function AllTests() {
$this->TestSuite(<strong>'All tests'</strong>);
<strong>$this->addFile('log_test.php');</strong>
}
}
?>
</pre>
L'inclusion de "autorun" permettra à notre future suite
de tests d'être lancée juste en invoquant ce script.
</p>
<p>
La sous-classe <span class="new_code">TestSuite</span> doit chaîner
son constructeur. Cette limitation sera supprimée dans
les versions à venir.
</p>
<p>
The method <span class="new_code">TestSuite::addFile()</span>
will include the test case file and read any new classes
that are descended from <span class="new_code">SimpleTestCase</span>.
Cette méthode <span class="new_code">TestSuite::addTestFile()</span> va
inclure le fichier de scénarios de test et lire parmi
toutes les nouvelles classes créées celles qui sont issues
de <span class="new_code">SimpleTestCase</span>.
<span class="new_code">UnitTestCase</span> est juste un exemple de classe dérivée
depuis <span class="new_code">SimpleTestCase</span> et vous pouvez créer les vôtres.
<span class="new_code">TestSuite::addFile()</span> peut aussi inclure d'autres suites.
</p>
<p>
La classe ne sera pas encore instanciée.
Quand la suite de tests est lancée, elle construira chaque instance
une fois le test atteint, et le détuira juste ensuite.
Cela veut dire que le constructeur n'est lancé qu'une fois avant
chaque initialisation de ce scénario de test et que le destructeur
est lui aussi lancé avant que le test suivant ne commence.
</p>
<p>
Il est commun de grouper des scénarios de test dans des super-classes
qui ne sont pas sensées être lancées, mais qui deviennent une classe de base
pour d'autres tests.
Pour que "autorun" fonctionne proprement les fichiers
des scénarios de test ne devraient pas lancer aveuglement
d'autres extensions de scénarios de test qui ne lanceraient pas
effectivement des tests.
Cela pourrait aboutir à un mauvais comptages des scénarios de test
pendant la procédure.
Pas vraiement un problème majeure, mais pour éviter cet inconvénient
il suffit de marquer vos classes de base comme <span class="new_code">abstract</span>.
SimpleTest ne lance pas les classes abstraites. Et si vous utilisez encore
PHP4 alors une directive <span class="new_code">SimpleTestOptions::ignore()</span>
dans votre fichier de scénario de test aura le même effet.
</p>
<p>
Par ailleurs, le fichier avec le scénario de test ne devrait pas
avoir été inclus ailleurs. Sinon aucun scénario de test
ne sera inclus à ce groupe.
Ceci pourrait se transformer en un problème plus grave :
si des fichiers ont déjà été inclus par PHP alors la méthode
<span class="new_code">TestSuite::addFile()</span> ne les détectera pas.
</p>
<p>
Pour afficher les résultats, il est seulement nécessaire
d'invoquer <em>tests/all_tests.php</em> à partir du serveur
web.
</p>
<p>
Pour plus de renseignements des groupes de tests, voyez le
<a href="group_test_documentation.html">documentation sur le groupement des tests</a>.
</p>
<p><a class="target" name="mock"><h2>Utiliser les objets fantaisie</h2></a></p>
<p>
Avançons un peu plus dans le futur.
</p>
<p>
Supposons que notre class logging soit testée et terminée.
Supposons aussi que nous testons une autre classe qui ait
besoin d'écrire des messages de log, disons
<span class="new_code">SessionPool</span>. Nous voulons tester une méthode
qui ressemblera probablement à quelque chose comme...
<pre><strong>
class SessionPool {
...
function logIn($username) {
...
$this->_log->message('User $username logged in.');
...
}
...
}
</strong>
</pre>
Avec le concept de "réutilisation de code" comme fil
conducteur, nous utilisons notre class <span class="new_code">Log</span>. Un
scénario de test classique ressemblera peut-être à...
<pre>
<?php
require_once('simpletest/autorun.php');
require_once('../classes/log.php');
<strong>require_once('../classes/session_pool.php');</strong>
class <strong>TestOfSessionLogging</strong> extends UnitTestCase {
function setUp() {
<strong>@unlink('/temp/test.log');</strong>
}
function tearDown() {
<strong>@unlink('/temp/test.log');</strong>
}
function testLoggingInIsLogged() {
<strong>$log = new Log('/temp/test.log');
$session_pool = &new SessionPool($log);
$session_pool->logIn('fred');</strong>
$messages = file('/temp/test.log');
$this->assertEqual($messages[0], "User fred logged in.<strong>\n</strong>");
}
}
?>
</pre>
Nous expliquerons les méthodes <span class="new_code">setUp()</span>
et <span class="new_code">tearDown()</span> plus tard.
</p>
<p>
Le design de ce scénario de test n'est pas complètement
mauvais, mais on peut l'améliorer. Nous passons du temps à
tripoter les fichiers de log qui ne font pas partie de
notre test.
Pire, nous avons créé des liens de proximité
entre la classe <span class="new_code">Log</span> et ce test. Que se
passerait-il si nous n'utilisions plus de fichiers, mais la
bibliothèque <em>syslog</em> à la place ?
Cela veut dire que notre test <span class="new_code">TestOfSessionLogging</span>
enverra un échec alors même qu'il ne teste pas Logging.
</p>
<p>
Il est aussi fragile sur des petites retouches. Avez-vous
remarqué le retour chariot supplémentaire à la fin du
message ? A-t-il été ajouté par le loggueur ? Et si il
ajoutait aussi un timestamp ou d'autres données ?
</p>
<p>
L'unique partie à tester réellement est l'envoi d'un
message précis au loggueur.
Nous pouvons réduire le couplage en
créant une fausse classe de logging : elle ne fait
qu'enregistrer le message pour le test, mais ne produit
aucun résultat. Sauf qu'elle doit ressembler exactement à
l'original.
</p>
<p>
Si l'objet fantaisie n'écrit pas dans un fichier alors nous
nous épargnons la suppression du fichier avant et après le
test. Nous pourrions même nous épargner quelques lignes de
code supplémentaires si l'objet fantaisie pouvait exécuter
l'assertion.
<p>
</p>
Trop beau pour être vrai ? Pas vraiement on peut créer un tel
objet très facilement...
<pre>
<?php
require_once('simpletest/autorun.php');
require_once('../classes/log.php');
require_once('../classes/session_pool.php');
<strong>Mock::generate('Log');</strong>
class TestOfSessionLogging extends UnitTestCase {
function testLoggingInIsLogged() {<strong>
$log = &new MockLog();
$log->expectOnce('message', array('User fred logged in.'));</strong>
$session_pool = &new SessionPool(<strong>$log</strong>);
$session_pool->logIn('fred');
}
}
?>
</pre>
L'appel <span class="new_code">Mock::generate()</span> a généré
une nouvelle classe appelé <span class="new_code">MockLog</span>.
Cela ressemble à un clone identique, sauf que nous pouvons
y adjoindre du code de test.
C'est ce que fait <span class="new_code">expectOnce()</span>.
Cela dit que si <span class="new_code">message()</span> m'est appelé,
il a intérêt à l'être avec le paramètre
"User fred logged in.".
</p>
<p>
L'appel <span class="new_code">tally()</span> est nécessaire pour annoncer à
l'objet fantaisie qu'il n'y aura plus d'appels ultérieurs.
Sans ça l'objet fantaisie pourrait attendre pendant une
éternité l'appel de la méthode sans jamais prévenir le
scénario de test. Les autres tests sont déclenchés
automatiquement quand l'appel à <span class="new_code">message()</span> est
invoqué sur l'objet <span class="new_code">MockLog</span> par le code
<span class="new_code">SessionPool::logIn()</span>.
L'appel <span class="new_code">mock</span> va déclencher une comparaison des
paramètres et ensuite envoyer le message "pass" ou "fail"
au test pour l'affichage. Des jokers peuvent être inclus
pour ne pas avoir à tester tous les paramètres d'un appel
alors que vous ne souhaitez qu'en tester un.
</p>
<p>
Les objets fantaisie dans la suite SimpleTest peuvent avoir
un ensemble de valeurs de sortie arbitraires, des séquences
de sorties, des valeurs de sortie sélectionnées à partir
des arguments d'entrée, des séquences de paramètres
attendus et des limites sur le nombre de fois qu'une
méthode peut être invoquée.
</p>
<p>
Pour que ce test fonctionne la librairie avec les objets
fantaisie doit être incluse dans la suite de tests, par
exemple dans <em>all_tests.php</em>.
</p>
<p>
Pour plus de renseignements sur les objets fantaisie, voyez le
<a href="mock_objects_documentation.html">documentation sur les objets fantaisie</a>.
</p>
<p><a class="target" name="web"><h2>Tester une page web</h2></a></p>
<p>
Une des exigences des sites web, c'est qu'ils produisent
des pages web. Si vous construisez un projet de A à Z et
que vous voulez intégrer des tests au fur et à mesure alors
vous voulez un outil qui puisse effectuer une navigation
automatique et en examiner le résultat. C'est le boulot
d'un testeur web.
</p>
<p>
Effectuer un test web via SimpleTest reste assez primitif :
il n'y a pas de javascript par exemple.
La plupart des autres opérations d'un navigateur sont simulées.
</p>
<p>
Pour vous donner une idée, voici un exemple assez trivial :
aller chercher une page web,
à partir de là naviguer vers la page "about"
et finalement tester un contenu déterminé par le client.
<pre>
<?php
require_once('simpletest/autorun.php');
<strong>require_once('simpletest/web_tester.php');</strong>
class TestOfAbout extends <strong>WebTestCase</strong> {
function testOurAboutPageGivesFreeReignToOurEgo() {
<strong>$this->get('http://test-server/index.php');
$this->click('About');
$this->assertTitle('About why we are so great');
$this->assertText('We are really great');</strong>
}
}
?>
</pre>
Avec ce code comme test de recette, vous pouvez vous
assurer que le contenu corresponde toujours aux
spécifications à la fois des développeurs et des autres
parties prenantes au projet.
</p>
<p>
Vous pouvez aussi naviguer à travers des formulaires...
<pre>
<?php
require_once('simpletest/autorun.php');
require_once('simpletest/web_tester.php');
class TestOfRankings extends WebTestCase {
function testWeAreTopOfGoogle() {
$this->get('http://google.com/');
$this->setField('q', 'simpletest');
$this->click("I'm Feeling Lucky");
$this->assertTitle('SimpleTest - Unit Testing for PHP');
}
}
?>
</pre>
...même si cela pourrait constituer une violation
des documents juridiques de Google(tm).
</p>
<p>
Pour plus de renseignements sur comment tester une page web, voyez la
<a href="web_tester_documentation.html">documentation sur tester des scripts web</a>.
</p>
<p>
<a href="http://sourceforge.net/projects/simpletest/"><img src="http://sourceforge.net/sflogo.php?group_id=76550&type=5" width="210" height="62" border="0" alt="SourceForge.net Logo"></a>
</p>
</div>
References and related information...
<ul>
<li>
<a href="https://sourceforge.net/project/showfiles.php?group_id=76550&release_id=153280">Télécharger PHP Simple Test</a>
depuis <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
</li>
<li>
L'<a href="http://simpletest.org/api/">API de SimpleTest pour développeur</a>
donne tous les détails sur les classes et assertions existantes.
</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>
|