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
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
<page title="Documentation sur les attentes" here="Les attentes">
<long_title>Documentation SimpleTest : tendre le testeur unitaire avec des classes d'attentes supplmentaires</long_title>
<content>
<section name="fantaisie" title="Plus de contrle sur les objets fantaisie">
<p>
Le comportement par dfaut des <a local="mock_objects_documentation">objets fantaisie</a> dans <a href="http://sourceforge.net/projects/simpletest/">SimpleTest</a> est soit une correspondance identique sur l'argument, soit l'acceptation de n'importe quel argument. Pour la plupart des tests, c'est suffisant. Cependant il est parfois ncessaire de ramollir un scnario de test.
</p>
<p>
Un des endroits o un test peut tre trop serr est la reconnaissance textuelle. Prenons l'exemple d'un composant qui produirait un message d'erreur utile lorsque quelque chose plante. Il serait utile de tester que l'erreur correcte est renvoye, mais le texte proprement dit risque d'tre plutt long. Si vous testez le texte dans son ensemble alors chaque modification de ce mme message -- mme un point ou une virgule -- vous aurez revenir sur la suite de test pour la modifier.
</p>
<p>
Voici un cas concret, nous avons un service d'actualits qui a chou dans sa tentative de connexion sa source distante.
<php><![CDATA[
<strong>class NewsService {
...
function publish(&$writer) {
if (! $this->isConnected()) {
$writer->write('Cannot connect to news service "' .
$this->_name . '" at this time. ' .
'Please try again later.');
}
...
}
}</strong>
]]></php>
L il envoie son contenu vers un classe <code>Writer</code>. Nous pourrions tester ce comportement avec un <code>MockWriter</code>...
<php><![CDATA[
class TestOfNewsService extends UnitTestCase {
...
function testConnectionFailure() {<strong>
$writer = &new MockWriter($this);
$writer->expectOnce('write', array(
'Cannot connect to news service ' .
'"BBC News" at this time. ' .
'Please try again later.'));
$service = &new NewsService('BBC News');
$service->publish($writer);
$writer->tally();</strong>
}
}
]]></php>
C'est un bon exemple d'un test fragile. Si nous dcidons d'ajouter des instructions complmentaires, par exemple proposer une source d'actualits alternative, nous casserons nos tests par la mme occasion sans pourtant avoir modifi une seule fonctionnalit.
</p>
<p>
Pour contourner ce problme, nous voudrions utiliser un test avec une expression rationnelle plutt qu'une correspondance exacte. Nous pouvons y parvenir avec...
<php><![CDATA[
class TestOfNewsService extends UnitTestCase {
...
function testConnectionFailure() {
$writer = &new MockWriter($this);<strong>
$writer->expectOnce(
'write',
array(new WantedPatternExpectation('/cannot connect/i')));</strong>
$service = &new NewsService('BBC News');
$service->publish($writer);
$writer->tally();
}
}
]]></php>
Plutt que de transmettre le paramtre attendu au <code>MockWriter</code>, nous envoyons une classe d'attente appele <code>WantedPatternExpectation</code>. L'objet fantaisie est suffisamment lgant pour reconnatre qu'il s'agit d'un truc spcial et pour le traiter diffremment. Plutt que de comparer l'argument entrant cet objet, il utilise l'objet attente lui-mme pour excuter le test.
</p>
<p>
<code>WantedPatternExpectation</code> utilise l'expression rationnelle pour la comparaison avec son constructeur. A chaque fois qu'une comparaison est fait travers <code>MockWriter</code> par rapport cette classe attente, elle fera un <code>preg_match()</code> avec ce motif. Dans notre scnario de test ci-dessus, aussi longtemps que la chane "cannot connect" apparat dans le texte, la fantaisie transmettra un succs au testeur unitaire. Peu importe le reste du texte.
</p>
<p>
Les classes attente possibles sont...
<table><tbody>
<tr><td><code>EqualExpectation</code></td><td>Une galit, plutt que la plus forte comparaison l'identique</td></tr>
<tr><td><code>NotEqualExpectation</code></td><td>Une comparaison sur la non-galit</td></tr>
<tr><td><code>IndenticalExpectation</code></td><td>La vrification par dfaut de l'objet fantaisie qui doit correspondre exactement</td></tr>
<tr><td><code>NotIndenticalExpectation</code></td><td>Inverse la logique de l'objet fantaisie</td></tr>
<tr><td><code>WantedPatternExpectation</code></td><td>Utilise une expression rationnelle Perl pour comparer une chane</td></tr>
<tr><td><code>NoUnwantedPatternExpectation</code></td><td>Passe seulement si l'expression rationnelle Perl choue</td></tr>
<tr><td><code>IsAExpectation</code></td><td>Vrifie le type ou le nom de la classe uniquement</td></tr>
<tr><td><code>NotAExpectation</code></td><td>L'oppos de <code>IsAExpectation</code></td></tr>
<tr><td><code>MethodExistsExpectation</code></td><td>Vrifie si la mthode est disponible sur un objet</td></tr>
</tbody></table>
La plupart utilisent la valeur attendue dans le constructeur. Les exceptions sont les vrifications sur motif, qui utilisent une expression rationnelle, ainsi que <code>IsAExpectation</code> et <code>NotAExpectation</code>, qui prennent un type ou un nom de classe comme chane.
</p>
</section>
<section name="comportement" title="Utiliser les attentes pour contrler les bouchons serveur">
<p>
Les classes attente peuvent servir autre chose que l'envoi d'assertions depuis les objets fantaisie, afin de choisir le comportement d'un <a local="mock_objects_documentation">objet fantaisie</a> ou celui d'un <a local="server_stubs_documentation">bouchon serveur</a>. A chaque fois qu'une liste d'arguments est donne, une liste d'objets d'attente peut tre insre la place.
</p>
<p>
Mettons que nous voulons qu'un bouchon serveur d'autorisation simule une connexion russie seulement si il reoit un objet de session valide. Nous pouvons y arriver avec ce qui suit...
<php><![CDATA[
Stub::generate('Authorisation');
<strong>
$authorisation = new StubAuthorisation();
$authorisation->setReturnValue(
'isAllowed',
true,
array(new IsAExpectation('Session', 'Must be a session')));
$authorisation->setReturnValue('isAllowed', false);</strong>
]]></php>
Le comportement par dfaut du bouchon serveur est dfini pour renvoyer <code>false</code> quand <code>isAllowed</code> est appel. Lorsque nous appelons cette mthode avec un unique paramtre qui est un objet <code>Session</code>, il renverra <code>true</code>. Nous avons aussi ajout un deuxime paramtre comme message. Il sera affich dans le message d'erreur de l'objet fantaisie si l'attente est la cause de l'chec.
</p>
<p>
Ce niveau de sophistication est rarement utile : il n'est inclut que pour tre complet.
</p>
</section>
<section name="etendre" title="Crer vos propres attentes">
<p>
Les classes d'attentes ont une structure trs simple. Tellement simple qu'il devient trs simple de crer vos propres version de logique pour des tests utiliss couramment.
</p>
<p>
Par exemple voici la cration d'une classe pour tester la validit d'adresses IP. Pour fonctionner correctement avec les bouchons serveurs et les objets fantaisie, cette nouvelle classe d'attente devrait tendre <code>SimpleExpectation</code>...
<php><![CDATA[
<strong>class ValidIp extends SimpleExpectation {
function test($ip) {
return (ip2long($ip) != -1);
}
function testMessage($ip) {
return "Address [$ip] should be a valid IP address";
}
}</strong>
]]></php>
Il n'y a vritablement que deux mthodes mettre en place. La mthode <code>test()</code> devrait renvoyer un <code>true</code> si l'attente doit passer, et une erreur <code>false</code> dans le cas contraire. La mthode <code>testMessage()</code> ne devrait renvoyer que du texte utile la comprhension du test en lui-mme.
</p>
<p>
Cette classe peut dsormais tre employe la place des classes d'attente prcdentes.
</p>
</section>
<section name="unitaire" title="Sous le capot du testeur unitaire">
<p>
Le <a href="http://sourceforge.net/projects/simpletest/">framework de test unitaire SimpleTest</a> utilise aussi dans son coeur des classes d'attente pour la <a local="unit_test_documentation">classe UnitTestCase</a>. Nous pouvons aussi tirer parti de ces mcanismes pour rutiliser nos propres classes attente l'intrieur mme des suites de test.
</p>
<p>
La mthode la plus directe est d'utiliser la mthode <code>SimpleTest::assertExpectation()</code> pour effectuer le test...
<php><![CDATA[
<strong>class TestOfNetworking extends UnitTestCase {
...
function testGetValidIp() {
$server = &new Server();
$this->assertExpectation(
new ValidIp(),
$server->getIp(),
'Server IP address->%s');
}
}</strong>
]]></php>
C'est plutt sale par rapport notre syntaxe habituelle du type <code>assert...()</code>.
</p>
<p>
Pour un cas aussi simple, nous crons d'ordinaire une mthode d'assertion distincte en utilisant la classe d'attente. Supposons un instant que notre attente soit un peu plus complique et que par consquent nous souhaitions la rutiliser, nous obtenons...
<php><![CDATA[
class TestOfNetworking extends UnitTestCase {
...<strong>
function assertValidIp($ip, $message = '%s') {
$this->assertExpectation(new ValidIp(), $ip, $message);
}</strong>
function testGetValidIp() {
$server = &new Server();<strong>
$this->assertValidIp(
$server->getIp(),
'Server IP address->%s');</strong>
}
}
]]></php>
Il est peu probable que nous ayons besoin de ce niveau de contrle sur la machinerie de test. Il est assez rare que le besoin d'une attente dpasse le stade de la reconnaissance d'un motif. De plus, les classes d'attente complexes peuvent rendre les tests difficiles lire et dboguer. Ces mcanismes sont vritablement l pour les auteurs de systme qui tendront le framework de test pour leurs propres outils de test.
</p>
</section>
</content>
<internal>
<link>
Utiliser les attentes <a href="#fantaisie">pour des tests plus prcis avec des objets fantaisie</a>
</link>
<link>
<a href="#comportement">Changer le comportement d'un objet fantaisie</a> avec des attentes
</link>
<link>
<a href="#etendre">Crer des attentes</a>
</link>
<link>
Par dessous SimpleTest <a href="#unitaire">utilise des classes d'attente</a>
</link>
</internal>
<external>
<link>
La page du projet SimpleTest sur <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
</link>
<link>
La page de tlchargement de SimpleTest sur <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
</link>
<link>
Les attentes imitent les contraintes dans <a href="http://www.jmock.org/">JMock</a>.
</link>
<link>
<a href="http://simpletest.sourceforge.net/">L'API complte pour SimpleTest</a> ralis avec PHPDoc.
</link>
</external>
<meta>
<keywords>
objets fantaisie,
dveloppement pilot par les tests,
hritage des attentes,
contraintes d'objet fantaisie,
test unitaire avanc en PHP,
test en premier,
architecture de framework de test
</keywords>
</meta>
</page>
|