File: first_test_tutorial.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 (285 lines) | stat: -rw-r--r-- 20,156 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
<?xml version="1.0" encoding="ISO-8859-1" ?>
<page title="Crer un nouveau de scnario de test" here="Tutorial : les tests unitaires en PHP">
    <long_title>Tutorial sur les tests unitaires en PHP - Crer un exemple de scnario de test en PHP</long_title>
    <content>
        <p>
            Si vous dbutez avec les tests unitaires, il est recommand d'essayer le code au fur et  mesure. Il n'y pas grand chose  taper et vous sentirez le rythme de la programmation pilote par les tests.
        </p>
        <p>
            Pour excuter les exemples tels quels, vous aurez besoin de crer un nouveau rpertoire et d'y installer trois dossiers : <em>classes</em>, <em>tests</em> et <em>temp</em>. Dzippez le framework <a href="simple_test.php">SimpleTest</a> dans le dossier <em>tests</em> et assurez vous que votre serveur web puisse atteindre ces endroits.
        </p>
        <p>
            <a class="target" name="nouveau"><h2>Un nouveau scnario de test</h2></a>
        </p>
        <p>
            L'exemple dans <a local="{{simple_test}}">l'introduction rapide</a> comprenait les tests unitaires d'une simple classe de log. Dans ce tutorial  propos de Simple Test, je vais essayer de raconter toute l'histoire du dveloppement de cette classe. Cette classe PHP est courte et simple : au cours de cette introduction, elle recevra beaucoup plus d'attention que dans le cadre d'un dveloppement de production. Nous verrons que derrire son apparente simplicit se cachent des choix de conception tonnamment difficiles.
        </p>
        <p>
            Peut-tre que ces choix sont trop difficiles ? Plutt que d'essayer de penser  tout en amont, je vais commencer par poser une exigence : nous voulons crire des messages dans un fichier. Ces messages doivent tre ajouts en fin de fichier s'il existe. Plus tard nous aurons besoin de priorits, de filtres et d'autres choses encore, mais nous plaons l'criture dans un fichier au coeur de nos proccupations. Nous ne penserons  rien d'autres par peur de confusion. OK, commenons par crire un test...
<php><![CDATA[
<strong><?php
    if (! defined('SIMPLE_TEST')) {
        define('SIMPLE_TEST', 'simpletest/');
    }
    require_once(SIMPLE_TEST . 'unit_tester.php');
    require_once(SIMPLE_TEST . 'reporter.php');

    class TestOfLogging extends UnitTestCase {
        function TestOfLogging() {
            $this->UnitTestCase();
        }
        function testCreatingNewFile() {
        }
    }
    
    $test = &new TestOfLogging();
    $test->run(new HtmlReporter());
?></strong>
]]></php>
            Pas  pas, voici ce qu'il veut dire.
        </p>
        <p>
            La constante <code>SIMPLE_TEST</code> contient le chemin vers les classes de Simple Test  partir de ce fichier. Les classes pourraient tre places dans le path du fichier <em>php.ini</em> mais si vous tes sur un serveur mutualis, vous n'y aurez probablement pas accs. Pour que tout le monde soit content, le chemin est dclar explicitement dans le script de test. Plus tard nous verrons comment tout finira au mme endroit.
        </p>
        <p>
            Demander la librairie <em>unit_tester.php</em> est assez vident mais qu'est-ce que ce fichier <em>reporter.php</em> ? Les librairies Simple Test sont une bote  outils pour crer votre propre suite de tests standardiss. Elles peuvent tre utilises &quot;telles quelles&quot; sans problme, mais elles sont constitues de plusieurs lments qui ont besoin d'tre assembls les uns aux autres. Le composant pour l'affichage est situ dans <em>reporter.php</em>. Probablement qu'un jour vous crirez le vtre : c'est pourquoi son inclusion est optionnelle. Simple Test contient une classe d'affichage, fonctionnelle mais basique : elle s'appelle <code>HtmlReporter</code>. Elle peut enregistrer des informations sur les tests : dbut, fin, erreur, russite ou chec. Elle affiche cette information le plus rapidement possible au cas o le code de test planterait et masquerait le lieu de l'chec.
        </p>
        <p>
            Les tests eux-mmes sont rassembls dans une classe de scnario de test. Cette dernire est typiquement une extension de la classe <code>UnitTestCase</code>. Quand le test est excut, elle cherche les mthodes commenant par &quot;test&quot; et les lancent. Notre seule mthode de test pour l'instant est appelle <code>testCreatingNewFile()</code> mais elle est encore vide.
        </p>
        <p>
            Une mthode vide ne fait rien. Nous avons besoin d'y placer du code. La classe <code>UnitTestCase</code> gnre des vnements de test  son excution : ces vnements sont envoys vers un observateur. La mthode <code>UnitTestCase::run()</code> lancent tous les tests de la classe.
        </p>
        <p>
            Et pour ajouter du code de test...
<php><![CDATA[
<?php
    if (! defined('SIMPLE_TEST')) {
        define('SIMPLE_TEST', 'simpletest/');
    }
    require_once(SIMPLE_TEST . 'unit_tester.php');
    require_once(SIMPLE_TEST . 'reporter.php');<strong>
    require_once('../classes/log.php');</strong>

    class TestOfLogging extends UnitTestCase {
        function TestOfLogging() {
            $this->UnitTestCase();
        }
        function testCreatingNewFile() {<strong>
            @unlink('../temp/test.log');
            $log = new Log('../temp/test.log');
            $log->message('Should write this to a file');
            $this->assertTrue(file_exists('../temp/test.log'));</strong>
        }
    }
    
    $test = &new TestOfLogging();
    $test->run(new HtmlReporter());
?>
]]></php>
        </p>
        <p>
            Vous pensez probablement que a reprsente beaucoup de code pour un unique test et je suis d'accord avec vous. Ne vous inquitez pas. Il s'agit d'un cot fixe et  partir de maintenant nous pouvons ajouter des tests  : une ligne ou presque  chaque fois. Parfois moins en utilisant des artefacts de test que nous dcouvrirons plus tard.
        </p>
        <p>
            Nous devons maintenant prendre nos premires dcisions. Notre fichier de test s'appelle <em>log_test.php</em> (n'importe quel nom ferait l'affaire) : nous le plaons dans un dossier appel <em>tests</em> (partout ailleurs serait aussi bien). Notre fichier de code s'appelle <em>log.php</em> : c'est son contenu que nous allons tester. Je l'ai plac dans notre dossier <em>classes</em> : cela veut-il dire que nous construisons une classe ?
        </p>
        <p>
            Pour cet exemple, la rponse est oui, mais le testeur unitaire n'est pas restreint aux tests de classe. C'est juste que le code orient objet est plus facile  dpecer et  remodeler. Ce n'est pas par hasard si la conduite de tests fins via les tests unitaires est apparue au sein de la communaut OO.
        </p>
        <p>
            Le test en lui-mme est minimal. Tout d'abord il limine tout autre fichier de test qui serait encore prsent. Les dcisions de conception arrivent ensuite en rafale. Notre classe s'appelle <code>Log</code> : elle passe le chemin du fichier au constructeur. Nous crons le log et nous lui envoyons aussitt un message en utilisant la mthode <code>message()</code>. L'originalit dans le nommage n'est pas une caractristique dsirable chez un dveloppeur informatique : c'est triste mais c'est comme a.
        </p>
        <p>
            La plus petite unit d'un test mmm... heu... unitaire est l'assertion. Ici nous voulons nous assurer que le fichier log auquel nous venons d'envoyer un message a bel et bien t cr. <code>UnitTestCase::assertTrue()</code> enverra un vnement russite si la condition value est vraie ou un chec dans le cas contraire. Nous pouvons avoir un ensemble d'assertions diffrentes et encore plus si nous tendons nos scnarios de test classique. Voici la liste...
            <table><tbody>
                <tr><td><code>assertTrue($x)</code></td><td>Echoue si $x est faux</td></tr>
                <tr><td><code>assertFalse($x)</code></td><td>Echoue si $x est vrai</td></tr>
                <tr><td><code>assertNull($x)</code></td><td>Echoue si $x est initialis</td></tr>
                <tr><td><code>assertNotNull($x)</code></td><td>Echoue si $x n'est pas initialis</td></tr>
                <tr><td><code>assertIsA($x, $t)</code></td><td>Echoue si $x n'est pas de la classe ou du type $t</td></tr>
                <tr><td><code>assertEqual($x, $y)</code></td><td>Echoue si $x == $y est faux</td></tr>
                <tr><td><code>assertNotEqual($x, $y)</code></td><td>Echoue si $x == $y est vrai</td></tr>
                <tr><td><code>assertIdentical($x, $y)</code></td><td>Echoue si $x === $y est faux</td></tr>
                <tr><td><code>assertNotIdentical($x, $y)</code></td><td>Echoue si $x === $y est vrai</td></tr>
                <tr><td><code>assertReference($x, $y)</code></td><td>Echoue sauf si $x et $y sont la mme variable</td></tr>
                <tr><td><code>assertCopy($x, $y)</code></td><td>Echoue si $x et $y sont la mme variable</td></tr>
                <tr><td><code>assertWantedPattern($p, $x)</code></td><td>Echoue sauf si l'expression rationnelle $p capture $x</td></tr>
                <tr><td><code>assertNoUnwantedPattern($p, $x)</code></td><td>Echoue si l'expression rationnelle $p capture $x</td></tr>
                <tr><td><code>assertNoErrors()</code></td><td>Echoue si une erreur PHP arrive</td></tr>
                <tr><td><code>assertError($x)</code></td><td>Echoue si aucune erreur ou message incorrect de PHP n'arrive</td></tr>
            </tbody></table>
        </p>
        <p>
            Nous sommes dsormais prt  lancer notre script de test en le passant dans le navigateur. Qu'est-ce qui devrait arriver ? Il devrait planter...
            <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>
            La raison ? Nous n'avons pas encore cr <em>log.php</em>.
        </p>
        <p>
            Mais attendez une minute, c'est idiot ! Ne me dites pas qu'il faut crer un test sans crire le code  tester auparavant...
        </p>
        <p>
            <a class="target" name="tdd"><h2>Dveloppement pilot par les tests</h2></a>
        </p>
        <p>
            Co-inventeur de l'<a href="http://www.extremeprogramming.org/">Extreme Programming</a>, Kent Beck a lanc un autre manifeste. Le livre est appel <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0321146530/ref=lib_dp_TFCV/102-2696523-7458519?v=glance&amp;s=books&amp;vi=reader#reader-link">Test Driven Development</a> (Dveloppement Pilot par les Tests)
            ou TDD et lve les tests unitaires  une position leve de la conception. En quelques mots, vous crivez d'abord un petit test et seulement ensuite le code qui passe ce test. N'importe quel bout de code. Juste pour qu'il passe.
        </p>
        <p>
            Vous crivez un autre test et puis de nouveau du code qui passe. Vous aurez alors un peu de duplication et gnralement du code pas trs propre. Vous remaniez (factorisez) ce code-l en vous assurant que les tests continuent  passer : vous ne pouvez rien casser. Une fois que le code est le plus propre possible vous tes prt  ajouter des nouvelles fonctionnalits. Il suffit juste de rajouter des nouveaux tests et de recommencer le cycle une nouvelle fois.

        </p>
        <p>
            Il s'agit d'une approche assez radicale et j'ai parfois l'impression qu'elle est incomplte. Mais il s'agit d'un moyen efficace pour expliquer un testeur unitaire ! Il se trouve que nous avons un test qui choue, pour ne pas dire qu'il plante : l'heure est venue d'crire du code dans <em>log.php</em>...
<php><![CDATA[
<strong><?php
    
    class Log {
        
        function Log($file_path) {
        }
        
        function message($message) {
        }
    }
?></strong>
]]></php>
            Il s'agit l du minimum que nous puissions faire pour viter une erreur fatale de PHP. Et maintenant la rponse devient...
            <div class="demo">
                <h1>testoflogging</h1>
                <span class="fail">Fail</span>: testcreatingnewfile-&gt;True assertion failed.<br />
                <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
                <strong>0</strong> passes and <strong>1</strong> fails.</div>
            </div>
            &quot;testoflogging&quot; a chou. Parmi les dfauts de PHP on trouve cette fcheuse tendance  transformer intrieurement les noms de classes et de mthodes en minuscules. SimpleTest utilise ces noms par dfaut pour dcrire les tests mais nous pouvons les remplacer par nos propres noms.
<php><![CDATA[
class TestOfLogging extends UnitTestCase {
    function TestOfLogging() {<strong>
        $this->UnitTestCase('Log class test');</strong>
    }
    function testCreatingNewFile() {
        @unlink('../temp/test.log');
        $log = new Log('../temp/test.log');
        $log->message('Should write this to a file');<strong>
        $this->assertTrue(file_exists('../temp/test.log'), 'File created');</strong>
    }
}
]]></php>
            Ce qui donne...
            <div class="demo">
                <h1>Log class test</h1>
                <span class="fail">Fail</span>: testcreatingnewfile-&gt;File created.<br />
                <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
                <strong>0</strong> passes and <strong>1</strong> fails.</div>
            </div>
            Par contre pour le nom des mthodes il n'y a rien  faire, dsol.
        </p>
        <p>
            Les messages d'un test comme ceux-ci ressemblent  bien des gards  des commentaires de code. Certains ne jurent que par eux, d'autres au contraire les bannissent purement et simplement en les considrant aussi encombrants qu'inutiles. Pour ma part, je me situe quelque part au milieu.
        </p>
        <p>
            Pour que le test passe, nous pourrions nous contenter de crer le fichier dans le constructeur de <code>Log</code>. Cette technique &quot;en faisant semblant&quot; est trs utile pour vrifier que le test fonctionne pendant les passages difficiles. Elle le devient encore plus si vous sortez d'un passage avec des tests ayant chous et que vous voulez juste vrifier de ne pas avoir oubli un truc bte. Nous n'allons pas aussi lentement donc...
<php><![CDATA[
<?php   
    class Log {<strong>
        var $_file_path;</strong>
        
        function Log($file_path) {<strong>
            $this->_file_path = $file_path;</strong>
        }
        
        function message($message) {<strong>
            $file = fopen($this->_file_path, 'a');
            fwrite($file, $message . "\n");
            fclose($file);</strong>
        }
    }
?>
]]></php>
            Au total, pas moins de 4 checs ont t ncessaire pour passer  l'tape suivante. Je n'avais pas cr le rpertoire temporaire, je ne lui avais pas donn les droits d'criture, j'avais une coquille et je n'avais pas non plus ajout ce nouveau rpertoire dans CVS. N'importe laquelle de ces erreurs aurait pu m'occuper pendant plusieurs heures si elle tait apparue plus tard mais c'est bien pour ces cas l qu'on teste. Avec les corrections adquates, a donne...
            <div class="demo">
                <h1>Log class test</h1>
                <div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
                <strong>1</strong> passes and <strong>0</strong> fails.</div>
            </div>
            a marche!
        </p>
        <p>
            Peut-tre n'aimez-vous pas le style plutt minimal de l'affichage. Les succs ne sont pas montrs par dfaut puisque gnralement vous n'avez pas besoin de plus d'information quand vous comprenez effectivement ce qui se passe. Dans le cas contraire, pensez  crire d'autres tests.
        </p>
        <p>
            D'accord, c'est assez strict. Si vous voulez aussi voir les succs alors vous pouvez <a local="display_subclass_tutorial">crer une sous-classe de <code>HtmlReporter</code></a> et l'utiliser pour les tests. Mme moi j'aime bien ce confort parfois.
        </p>
        <p>
            <a class="target" name="doc"><h2>Les tests comme documentation</h2></a>
        </p>
        <p>
            Il y a une nuance ici. Nous ne voulons pas crer de fichier avant d'avoir effectivement envoy de message. Plutt que d'y rflchir trop longtemps, nous allons juste ajouter un test pour a.
<php><![CDATA[
class TestOfLogging extends UnitTestCase {
    function TestOfLogging() {
        $this->UnitTestCase('Log class test');
    }
    function testCreatingNewFile() {
        @unlink('../temp/test.log');
        $log = new Log('../temp/test.log');<strong>
        $this->assertFalse(file_exists('../temp/test.log'), 'No file created before first message');</strong>
        $log->message('Should write this to a file');
        $this->assertTrue(file_exists('../temp/test.log'), 'File created');
    }
}
]]></php>
            ...et dcouvrir que a marche dj...
            <div class="demo">
                <h1>Log class test</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>
            En fait je savais que a allait tre le cas. J'ajoute ce test de confirmation tout d'abord pour garder l'esprit tranquille, mais aussi pour documenter ce comportement. Ce petit test supplmentaire dans son contexte en dit plus long qu'un scnario utilisateur d'une douzaine de lignes ou qu'un diagramme UML complet. Que la suite de tests devienne une source de documentation est un effet secondaire assez agrable.
        </p>
        <p>
            Devrions-nous supprimer le fichier temporaire  la fin du test ? Par habitude, je le fais une fois que j'en ai termin avec la mthode de test et qu'elle marche. Je n'ai pas envie de valider du code qui laisse des restes de fichiers de test traner aprs un test. Mais je ne le fais pas non plus pendant que j'cris le code. Peut-tre devrais-je, mais parfois j'ai besoin de voir ce qui se passe : on retrouve cet aspect confort voqu plus haut.
        </p>
        <p>
            Dans un vritable projet, nous avons habituellement plus qu'un unique scnario de test : c'est pourquoi nous allons regarder comment <a local="group_test_tutorial">grouper des tests dans des suites de tests</a>.
        </p>
    </content>
    <internal>
        <link>Crer un <a href="#nouveau">nouveau scnario de test</a>.</link>
        <link>Le <a href="#tdd">Dveloppement Pilot par les Tests</a> en PHP.</link>
        <link>Les <a href="#doc">tests comme documentation</a> est un des nombreux effets secondaires.</link>
    </internal>
    <external>
        <link>
            La <a href="http://junit.sourceforge.net/doc/faq/faq.htm">FAQ de JUnit</a> contient plein de conseils judicieux sur les tests.
        </link>
        <link>
            <a href="group_test_tutorial.php">Ensuite</a> vient &quot;comment grouper des scnarios de tests ensemble&quot;.
        </link>
        <link>
            Vous aurez besoin du <a href="simple_test.php">framework de test SimpleTest</a> pour ces exemples.
        </link>
    </external>
    <meta>
        <keywords>
            dveloppement logiciel,
            programmation php,
            outils de dveloppement logiciel,
            tutorial php,
            scripts php gratuits,
            architecture,
            ressouces php,
            objets fantaisie,
            junit,
            test php,
            test unitaire,
            test php automatis,
            tutorial de scnarios de test,
            explication d'un scnario de test unitaire,
            exemple de test unitaire
        </keywords>
    </meta>
</page>