File: mock_objects_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 (269 lines) | stat: -rw-r--r-- 14,178 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
<?xml version="1.0" encoding="ISO-8859-1" ?>
<page title="Objets fantaisie" here="Utiliser des objets fantaisie">
    <long_title>tutorial sur les tests unitaires en PHP - Utiliser les objets fantaisie en PHP</long_title>
    <content>
        <p>
            <a class="target" name="remaniement"><h2>Remanier les tests  nouveau</h2></a>
        </p>
        <p>
            Avant d'ajouter de nouvelles fonctionnalits il y a du remaniement  faire. Nous allons effectuer des tests chronomtrs et la classe <code>TimeTestCase</code> a dfinitivement besoin d'un fichier propre. Appelons le <em>tests/time_test_case.php</em>...
<php><![CDATA[
<strong><?php
    if (! defined('SIMPLE_TEST')) {
        define('SIMPLE_TEST', 'simpletest/');
    }
    require_once(SIMPLE_TEST . 'unit_tester.php');

    class TimeTestCase extends UnitTestCase {
        function TimeTestCase($test_name = '') {
            $this->UnitTestCase($test_name);
        }
        function assertSameTime($time1, $time2, $message = '') {
            if (! $message) {
                $message = "Time [$time1] should match time [$time2]";
            }
            $this->assertTrue(
                    ($time1 == $time2) || ($time1 + 1 == $time2),
                    $message);
        }
    }
?></strong>
]]></php>
            Nous pouvons lors utiliser <code>require()</code> pour incorporer ce fichier dans le script <em>all_tests.php</em>.
        </p>
        <p>
            <a class="target" name="timestamp"><h2>Ajouter un timestamp au Log</h2></a>
        </p>
        <p>
            Je ne sais pas trop quel devrait tre le format du message de log pour le test alors pour vrifier le timestamp nous pourrions juste faire la plus simple des choses possibles, c'est  dire rechercher une suite de chiffres.
<php><![CDATA[
<?php
    require_once('../classes/log.php');<strong>
    require_once('../classes/clock.php');

    class TestOfLogging extends TimeTestCase {
        function TestOfLogging() {
            $this->TimeTestCase('Log class test');
        }</strong>
        function setUp() {
            @unlink('../temp/test.log');
        }
        function tearDown() {
            @unlink('../temp/test.log');
        }
        function getFileLine($filename, $index) {
            $messages = file($filename);
            return $messages[$index];
        }
        function testCreatingNewFile() {
            ...
        }
        function testAppendingToFile() {
            ...
        }<strong>
        function testTimestamps() {
            $log = new Log('../temp/test.log');
            $log->message('Test line');
            $this->assertTrue(
                    preg_match('/(\d+)/', $this->getFileLine('../temp/test.log', 0), $matches),
                    'Found timestamp');
            $clock = new clock();
            $this->assertSameTime((integer)$matches[1], $clock->now(), 'Correct time');
        }</strong>
    }
?>
]]></php>
            Ce scnario de test cre un nouvel objet <code>Log</code> et crit un message. Nous recherchons une suite de chiffres et nous la comparons  l'horloge prsente en utilisant notre objet <code>Clock</code>. Bien sr a ne marche pas avant d'avoir crit le code.
            <div class="demo">
                <h1>All tests</h1>
                <span class="pass">Pass</span>: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 1/] in [Test line 1]<br />
                <span class="pass">Pass</span>: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 2/] in [Test line 2]<br />
                <span class="pass">Pass</span>: log_test.php->Log class test->testcreatingnewfile->Created before message<br />
                <span class="pass">Pass</span>: log_test.php->Log class test->testcreatingnewfile->File created<br />
                <span class="fail">Fail</span>: log_test.php->Log class test->testtimestamps->Found timestamp<br />
                <br />
                <b>Notice</b>:  Undefined offset:  1 in <b>/home/marcus/projects/lastcraft/tutorial_tests/tests/log_test.php</b> on line <b>44</b><br />
                <span class="fail">Fail</span>: log_test.php->Log class test->testtimestamps->Correct time<br />
                <span class="pass">Pass</span>: clock_test.php->Clock class test->testclockadvance->Advancement<br />
                <span class="pass">Pass</span>: clock_test.php->Clock class test->testclocktellstime->Now is the right time<br />
                <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">3/3 test cases complete.
                <strong>6</strong> passes and <strong>2</strong> fails.</div>
            </div>
            Cette suite de tests montre encore les succs de notre modification prcdente.
        </p>
        <p>
            Nous pouvons faire passer les tests en ajoutant simplement un timestamp  l'criture dans le fichier. Oui, bien sr, tout ceci est assez trivial et d'habitude je ne le testerais pas aussi fanatiquement, mais a va illustrer un problme plus gnral... Le fichier <em>log.php</em> devient...
<php><![CDATA[
<?php<strong>
    require_once('../classes/clock.php');</strong>
    
    class Log {
        var $_file_path;
        
        function Log($file_path) {
            $this->_file_path = $file_path;
        }
        
        function message($message) {<strong>
            $clock = new Clock();</strong>
            $file = fopen($this->_file_path, 'a');<strong>
            fwrite($file, "[" . $clock->now() . "] $message\n");</strong>
            fclose($file);
        }
    }
?>
]]></php>
            Les tests devraient passer.
        </p>
        <p>
            Par contre notre nouveau test est plein de problmes. Qu'est-ce qui se passe si notre format de temps change ? Les choses vont devenir largement plus compliques si a venait  se produire. Cela veut aussi dire que n'importe quel changement du format de notre classe horloge causera aussi un chec dans les tests de log. Bilan : nos tests de log sont tout mlangs avec les test d'horloge et par la mme trs fragiles. Tester  la fois des facettes de l'horloge et d'autres du log manque de cohsion, ou de focalisation tanche si vous prfrez. Nos problmes sont causs en partie parce que le rsultat de l'horloge est imprvisible alors que l'unique chose  tester est la prsence du rsultat de <code>Clock::now()</code>. Peu importe le contenu de l'appel de cette mthode.
        </p>
        <p>
            Pouvons-nous rendre cet appel prvisible ? Oui si nous pouvons forcer le loggueur  utiliser une version factice de l'horloge lors du test. Cette classe d'horloge factice devrait se comporter exactement comme la classe <code>Clock</code>  part une sortie fixe dans la mthode <code>now()</code>. Et au passage, a nous affranchirait mme de la classe <code>TimeTestCase</code> !
        </p>
        <p>
            Nous pourrions crire une telle classe assez facilement mme s'il s'agit d'un boulot plutt fastidieux. Nous devons juste crer une autre classe d'horloge avec la mme interface sauf que la mthode <code>now()</code> retourne une valeur modifiable via une autre mthode d'initialisation. C'est plutt pas mal de travail pour un test plutt mineur.
        </p>
        <p>
            Sauf que a se fait sans aucun effort.
        </p>
        <p>
            <a class="target" name="fantaisie"><h2>Une horloge fantaisie</h2></a>
        </p>
        <p>
            Pour atteindre le nirvana de l'horloge instantan pour test nous n'avons besoin que de trois lignes de code supplmentaires...
<php><![CDATA[
require_once('simpletest/mock_objects.php');
]]></php>
            Cette instruction inclut le code de gnrateur d'objet fantaisie. Le plus simple reste de le mettre dans le script <em>all_tests.php</em> tant donn qu'il est utilis assez frquemment.
<php><![CDATA[
Mock::generate('Clock');
]]></php>
            C'est la ligne qui fait le travail. Le gnrateur de code scanne la classe, en extrait toutes ses mthodes, cre le code pour gnrer une classe avec une interface identique, mais en ajoutant le nom &quot;Mock&quot; et ensuite <code>eval()</code> le nouveau code pour crer la nouvelle classe.
<php><![CDATA[
$clock = &new MockClock($this);
]]></php>
            Cette ligne peut tre ajoute dans n'importe quelle mthode de test qui nous intresserait. Elle cre l'horloge fantaisie prte  recevoir nos instructions.
        </p>
        <p>
            Notre scnario de test en est  ses premiers pas vers un nettoyage radical...
<php><![CDATA[
<?php
    require_once('../classes/log.php');
    require_once('../classes/clock.php');<strong>
    Mock::generate('Clock');

    class TestOfLogging extends UnitTestCase {
        function TestOfLogging() {
            $this->UnitTestCase('Log class test');
        }</strong>
        function setUp() {
            @unlink('../temp/test.log');
        }
        function tearDown() {
            @unlink('../temp/test.log');
        }
        function getFileLine($filename, $index) {
            $messages = file($filename);
            return $messages[$index];
        }
        function testCreatingNewFile() {
            ...
        }
        function testAppendingToFile() {
            ...
        }
        function testTimestamps() {<strong>
            $clock = &new MockClock($this);
            $clock->setReturnValue('now', 'Timestamp');
            $log = new Log('../temp/test.log');
            $log->message('Test line', &$clock);
            $this->assertWantedPattern(
                    '/Timestamp/',
                    $this->getFileLine('../temp/test.log', 0),
                    'Found timestamp');</strong>
        }
    }
?>
]]></php>
            Cette mthode de test cre un objet <code>MockClock</code> puis dfinit la valeur retourn par la mthode <code>now()</code> par la chane &quot;Timestamp&quot;. A chaque fois que nous appelons <code>$clock->now()</code>, elle retournera cette mme chane. a devrait tre quelque chose de facilement reprable.
        </p>
        <p>
            Ensuite nous crons notre loggueur et envoyons un message. Nous incluons dans l'appel <code>message()</code> l'horloge que nous souhaitons utiliser. a veut dire que nous aurons  ajouter un paramtre optionnel  la classe de log pour rendre ce test possible...
<php><![CDATA[
class Log {
    var $_file_path;
    
    function Log($file_path) {
        $this->_file_path = $file_path;
    }
    
    function message($message, <strong>$clock = false</strong>) {<strong>
        if (!is_object($clock)) {
            $clock = new Clock();
        }</strong>
        $file = fopen($this->_file_path, 'a');
        fwrite($file, "[" . $clock->now() . "] $message\n");
        fclose($file);
    }
}
]]></php>
            Maintenant tous les tests passent et ils ne testent que le code du loggueur. Nous pouvons  nouveau respirer.
        </p>
        <p>
            Est-ce que ce paramtre supplmentaire dans la classe <code>Log</code> vous gne ? Nous n'avons chang l'interface que pour faciliter les tests aprs tout. Les interfaces ne sont-elles pas la chose la plus importante ? Avons nous souill notre classe avec du code de test ?
        </p>
        <p>
            Peut-tre, mais rflchissez  ce qui suit. A la prochaine occasion, regardez une carte avec des circuits imprims, peut-tre la carte mre de l'ordinateur que vous regardez actuellement. Sur la plupart d'entre elles vous trouverez un trou bizarre et vide ou alors un point de soudure sans rien de fix ou mme une pingle ou une prise sans aucune fonction vidente. Peut-tre certains sont l en prvision d'une expansion ou d'une variation future, mais la plupart n'y sont que pour les tests.
        </p>
        <p>
            Pensez-y. Les usines qui fabriquent ces cartes imprimes par centaine de milliers gaspillent des matires premires sur des pices qui n'ajoutent rien  la fonction finale. Si les ingnieurs matriel peuvent faire quelques sacrifices  l'lgance, je suis sr que nous pouvons aussi le faire. Notre sacrifice ne gaspille pas de matriel aprs tout.
        </p>
        <p>
            a vous gne encore ? En fait moi aussi, mais pas tellement ici. La priorit numro 1 reste du code qui marche, pas un prix pour minimalisme. Si a vous gne vraiment alors dplacez la cration de l'horloge dans une autre mthode mre protge. Ensuite sous classez l'horloge pour le test et crasez la mthode mre avec une qui renvoie le leurre. Vos tests sont bancals mais votre interface est intacte.
        </p>
        <p>
            Une nouvelle fois je vous laisse la dcision finale.
        </p>
    </content>
    <internal>
        <link>
            <a href="#remaniement">Remanier les tests</a> dans le but de rutiliser notre nouveau test de temps.
        </link>
        <link>Ajouter des <a href="#timestamp">timestamps de Log</a>.</link>
        <link><a href="#fantaisie">Crer une horloge fantaisie</a> pour rendre les tests cohsifs.</link>
    </internal>
    <external>
        <link>
            La section prcdente : <a href="first_test_tutorial.php">tutorial de test unitaire</a>.
        </link>
        <link>
            La section suivante : <a href="boundary_classes_tutorial.php">les frontires de l'application</a>.
        </link>
        <link>
            Vous aurez besoin du <a href="simple_test.php">framework de test SimpleTest</a> pour essayer ces exemples.
        </link>
        <link>
            Documents sur les <a href="http://www.mockobjects.com/">objets fantaisie</a>.
        </link>
    </external>
    <meta>
        <keywords>
            dveloppement logiciel,
            programmation php,
            outils de dveloppement logiciel,
            tutoriel php,
            scripts php gratuits,
            architecture,
            ressources php,
            objet fantaisie,
            junit,
            phpunit,
            simpletest,
            test php,
            outil de test unitaire,
            suite de test php
        </keywords>
    </meta>
</page>