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 "Mock" 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 "Timestamp". 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>
|