1
0
mirror of synced 2024-12-15 07:36:03 +03:00
doctrine2/vendor/simpletest/docs/fr/partial_mocks_documentation.html
2007-01-28 22:39:21 +00:00

334 lines
16 KiB
HTML

<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Documentation SimpleTest : les objets fantaisie partiels</title>
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
</head>
<body>
<div class="menu_back">
<div class="menu">
<h2>
<a href="index.html">SimpleTest</a>
</h2>
<ul>
<li>
<a href="overview.html">Overview</a>
</li>
<li>
<a href="unit_test_documentation.html">Unit tester</a>
</li>
<li>
<a href="group_test_documentation.html">Group tests</a>
</li>
<li>
<a href="server_stubs_documentation.html">Server stubs</a>
</li>
<li>
<a href="mock_objects_documentation.html">Mock objects</a>
</li>
<li>
<a href="partial_mocks_documentation.html">Partial mocks</a>
</li>
<li>
<a href="reporter_documentation.html">Reporting</a>
</li>
<li>
<a href="expectation_documentation.html">Expectations</a>
</li>
<li>
<a href="web_tester_documentation.html">Web tester</a>
</li>
<li>
<a href="form_testing_documentation.html">Testing forms</a>
</li>
<li>
<a href="authentication_documentation.html">Authentication</a>
</li>
<li>
<a href="browser_documentation.html">Scriptable browser</a>
</li>
</ul>
</div>
</div>
<h1>Documentation sur les objets fantaisie partiels</h1>
<div class="content">
<p>
Un objet fantaisie partiel n'est ni plus ni moins qu'un mod&egrave;le de conception pour soulager un probl&egrave;me sp&eacute;cifique du test avec des objets fantaisie, celui de placer des objets fantaisie dans des coins serr&eacute;s. Il s'agit d'un outil assez limit&eacute; et peut-&ecirc;tre m&ecirc;me une id&eacute;e pas si bonne que &ccedil;a. Elle est incluse dans SimpleTest pour la simple raison que je l'ai trouv&eacute;e utile &agrave; plus d'une occasion et qu'elle m'a &eacute;pargn&eacute;e pas mal de travail dans ces moments-l&agrave;.
</p>
<p>
<a class="target" name="injection">
<h2>Le probl&egrave;me de l'injection dans un objet fantaisie</h2>
</a>
</p>
<p>
Quand un objet en utilise un autre il est tr&egrave;s simple d'y faire circuler une version fantaisie d&eacute;j&agrave; pr&ecirc;te avec ses attentes. Les choses deviennent un peu plus d&eacute;licates si un objet en cr&eacute;e un autre et que le cr&eacute;ateur est celui que l'on souhaite tester. Cela revient &agrave; dire que l'objet cr&eacute;&eacute; devrait &ecirc;tre une fantaisie, mais nous pouvons difficilement dire &agrave; notre classe sous test de cr&eacute;er un objet fantaisie plut&ocirc;t qu'un "vrai" objet. La classe test&eacute;e ne sait m&ecirc;me pas qu'elle travaille dans un environnement de test.
</p>
<p>
Par exemple, supposons que nous sommes en train de construire un client telnet et qu'il a besoin de cr&eacute;er une socket r&eacute;seau pour envoyer ses messages. La m&eacute;thode de connexion pourrait ressemble &agrave; quelque chose comme...
<pre>
<strong>&lt;?php
require_once('socket.php');
class Telnet {
...
function &amp;connect($ip, $port, $username, $password) {
$socket = &amp;new Socket($ip, $port);
$socket-&gt;read( ... );
...
}
}
?&gt;</strong>
</pre>
Nous voudrions vraiment avoir une version fantaisie de l'objet socket, que pouvons nous faire ?
</p>
<p>
La premi&egrave;re solution est de passer la socket en tant que param&egrave;tre, ce qui force la cr&eacute;ation au niveau inf&eacute;rieur. Charger le client de cette t&acirc;che est effectivement une bonne approche si c'est possible et devrait conduire &agrave; un remaniement -- de la cr&eacute;ation &agrave; partir de l'action. En fait, c'est l&agrave; une des mani&egrave;res avec lesquels tester en s'appuyant sur des objets fantaisie vous force &agrave; coder des solutions plus resserr&eacute;es sur leur objectif. Ils am&eacute;liorent votre programmation.
</p>
<p>
Voici ce que &ccedil;a devrait &ecirc;tre...
<pre>
&lt;?php
require_once('socket.php');
class Telnet {
...
<strong>function &amp;connect(&amp;$socket, $username, $password) {
$socket-&gt;read( ... );
...
}</strong>
}
?&gt;
</pre>
Sous-entendu, votre code de test est typique d'un cas de test avec un objet fantaisie.
<pre>
class TelnetTest extends UnitTestCase {
...
function testConnection() {<strong>
$socket = &amp;new MockSocket($this);
...
$telnet = &amp;new Telnet();
$telnet-&gt;connect($socket, 'Me', 'Secret');
...</strong>
}
}
</pre>
C'est assez &eacute;vident que vous ne pouvez descendre que d'un niveau. Vous ne voudriez pas que votre application de haut niveau cr&eacute;e tous les fichiers de bas niveau, sockets et autres connexions &agrave; la base de donn&eacute;es dont elle aurait besoin. Elle ne conna&icirc;trait pas les param&egrave;tres du constructeur de toute fa&ccedil;on.
</p>
<p>
La solution suivante est de passer l'objet cr&eacute;&eacute; sous la forme d'un param&egrave;tre optionnel...
<pre>
&lt;?php
require_once('socket.php');
class Telnet {
...<strong>
function &amp;connect($ip, $port, $username, $password, $socket = false) {
if (!$socket) {
$socket = &amp;new Socket($ip, $port);
}
$socket-&gt;read( ... );</strong>
...
return $socket;
}
}
?&gt;
</pre>
Pour une solution rapide, c'est g&eacute;n&eacute;ralement suffisant. Ensuite le test est tr&egrave;s similaire : comme si le param&egrave;tre &eacute;tait transmis formellement...
<pre>
class TelnetTest extends UnitTestCase {
...
function testConnection() {<strong>
$socket = &amp;new MockSocket($this);
...
$telnet = &amp;new Telnet();
$telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret', &amp;$socket);
...</strong>
}
}
</pre>
Le probl&egrave;me de cette approche tient dans son manque de nettet&eacute;. Il y a du code de test dans la classe principale et aussi des param&egrave;tres transmis dans le sc&eacute;nario de test qui ne sont jamais utilis&eacute;s. Il s'agit l&agrave; d'une approche rapide et sale, mais qui ne reste pas moins efficace dans la plupart des situations.
</p>
<p>
Une autre solution encore est de laisser un objet fabrique s'occuper de la cr&eacute;ation...
<pre>
&lt;?php
require_once('socket.php');
class Telnet {<strong>
function Telnet(&amp;$network) {
$this-&gt;_network = &amp;$network;
}</strong>
...
function &amp;connect($ip, $port, $username, $password) {<strong>
$socket = &amp;$this-&gt;_network-&gt;createSocket($ip, $port);
$socket-&gt;read( ... );</strong>
...
return $socket;
}
}
?&gt;
</pre>
Il s'agit l&agrave; probablement de la r&eacute;ponse la plus travaill&eacute;e &eacute;tant donn&eacute; que la cr&eacute;ation est maintenant situ&eacute;e dans une petite classe sp&eacute;cialis&eacute;e. La fabrique r&eacute;seau peut &ecirc;tre test&eacute;e s&eacute;par&eacute;ment et utilis&eacute;e en tant que fantaisie quand nous testons la classe telnet...
<pre>
class TelnetTest extends UnitTestCase {
...
function testConnection() {<strong>
$socket = &amp;new MockSocket($this);
...
$network = &amp;new MockNetwork($this);
$network-&gt;setReturnReference('createSocket', $socket);
$telnet = &amp;new Telnet($network);
$telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
...</strong>
}
}
</pre>
Le probl&egrave;me reste que nous ajoutons beaucoup de classes &agrave; la biblioth&egrave;que. Et aussi que nous utilisons beaucoup de fabriques ce qui rend notre code un peu moins intuitif. La solution la plus flexible, mais aussi la plus complexe.
</p>
<p>
Peut-on trouver un juste milieu ?
</p>
<p>
<a class="target" name="creation">
<h2>M&eacute;thode fabrique prot&eacute;g&eacute;e</h2>
</a>
</p>
<p>
Il existe une technique pour palier &agrave; ce probl&egrave;me sans cr&eacute;er de nouvelle classe dans l'application; par contre elle induit la cr&eacute;ation d'une sous-classe au moment du test. Premi&egrave;rement nous d&eacute;pla&ccedil;ons la cr&eacute;ation de la socket dans sa propre m&eacute;thode...
<pre>
&lt;?php
require_once('socket.php');
class Telnet {
...
function &amp;connect($ip, $port, $username, $password) {<strong>
$socket = &amp;$this-&gt;_createSocket($ip, $port);</strong>
$socket-&gt;read( ... );
...
}<strong>
function &amp;_createSocket($ip, $port) {
return new Socket($ip, $port);
}</strong>
}
?&gt;
</pre>
Il s'agit l&agrave; de la seule modification dans le code de l'application.
</p>
<p>
Pour le sc&eacute;nario de test, nous devons cr&eacute;er une sous-classe de mani&egrave;re &agrave; intercepter la cr&eacute;ation de la socket...
<pre>
<strong>class TelnetTestVersion extends Telnet {
var $_mock;
function TelnetTestVersion(&amp;$mock) {
$this-&gt;_mock = &amp;$mock;
$this-&gt;Telnet();
}
function &amp;_createSocket() {
return $this-&gt;_mock;
}
}</strong>
</pre>
Ici j'ai d&eacute;plac&eacute; la fantaisie dans le constructeur, mais un setter aurait fonctionn&eacute; tout aussi bien. Notez bien que la fantaisie est plac&eacute;e dans une variable d'objet avant que le constructeur ne soit attach&eacute;. C'est n&eacute;cessaire dans le cas o&ugrave; le constructeur appelle <span class="new_code">connect()</span>. Autrement il pourrait donner un valeur nulle &agrave; partir de <span class="new_code">_createSocket()</span>.
</p>
<p>
Apr&egrave;s la r&eacute;alisation de tout ce travail suppl&eacute;mentaire le sc&eacute;nario de test est assez simple. Nous avons juste besoin de tester notre nouvelle classe &agrave; la place...
<pre>
class TelnetTest extends UnitTestCase {
...
function testConnection() {<strong>
$socket = &amp;new MockSocket($this);
...
$telnet = &amp;new TelnetTestVersion($socket);
$telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
...</strong>
}
}
</pre>
Cette nouvelle classe est tr&egrave;s simple bien s&ucirc;r. Elle ne fait qu'initier une valeur renvoy&eacute;e, &agrave; la mani&egrave;re d'une fantaisie. Ce serait pas mal non plus si elle pouvait v&eacute;rifier les param&egrave;tres entrants. Exactement comme un objet fantaisie. Il se pourrait bien que nous ayons &agrave; r&eacute;aliser cette astuce r&eacute;guli&egrave;rement : serait-il possible d'automatiser la cr&eacute;ation de cette sous-classe ?
</p>
<p>
<a class="target" name="partiel">
<h2>Un objet fantaisie partiel</h2>
</a>
</p>
<p>
Bien s&ucirc;r la r&eacute;ponse est "oui" ou alors j'aurais arr&ecirc;t&eacute; d'&eacute;crire depuis quelques temps d&eacute;j&agrave; ! Le test pr&eacute;c&eacute;dent a repr&eacute;sent&eacute; beaucoup de travail, mais nous pouvons g&eacute;n&eacute;rer la sous-classe en utilisant une approche &agrave; celle des objets fantaisie.
</p>
<p>
Voici donc une version avec objet fantaisie partiel du test...
<pre>
<strong>Mock::generatePartial(
'Telnet',
'TelnetTestVersion',
array('_createSocket'));</strong>
class TelnetTest extends UnitTestCase {
...
function testConnection() {<strong>
$socket = &amp;new MockSocket($this);
...
$telnet = &amp;new TelnetTestVersion($this);
$telnet-&gt;setReturnReference('_createSocket', $socket);
$telnet-&gt;Telnet();
$telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
...</strong>
}
}
</pre>
La fantaisie partielle est une sous-classe de l'original dont on aurait "remplac&eacute;" les m&eacute;thodes s&eacute;lectionn&eacute;es avec des versions de test. L'appel &agrave; <span class="new_code">generatePartial()</span> n&eacute;cessite trois param&egrave;tres : la classe &agrave; sous classer, le nom de la nouvelle classe et une liste des m&eacute;thodes &agrave; simuler.
</p>
<p>
Instancier les objets qui en r&eacute;sultent est plut&ocirc;t d&eacute;licat. L'unique param&egrave;tre du constructeur d'un objet fantaisie partiel est la r&eacute;f&eacute;rence du testeur unitaire. Comme avec les objets fantaisie classiques c'est n&eacute;cessaire pour l'envoi des r&eacute;sultats de test en r&eacute;ponse &agrave; la v&eacute;rification des attentes.
</p>
<p>
Une nouvelle fois le constructeur original n'est pas lanc&eacute;. Indispensable dans le cas o&ugrave; le constructeur aurait besoin des m&eacute;thodes fantaisie : elles n'ont pas encore &eacute;t&eacute; initi&eacute;es ! Nous initions les valeurs retourn&eacute;es &agrave; cet instant et ensuite lan&ccedil;ons le constructeur avec ses param&egrave;tres normaux. Cette construction en trois &eacute;tapes de "new", suivie par la mise en place des m&eacute;thodes et ensuite par la lancement du constructeur proprement dit est ce qui distingue le code d'un objet fantaisie partiel.
</p>
<p>
A part pour leur construction, toutes ces m&eacute;thodes fantaisie ont les m&ecirc;mes fonctionnalit&eacute;s que dans le cas des objets fantaisie et toutes les m&eacute;thodes non fantaisie se comportent comme avant. Nous pouvons mettre en place des attentes tr&egrave;s facilement...
<pre>
class TelnetTest extends UnitTestCase {
...
function testConnection() {
$socket = &amp;new MockSocket($this);
...
$telnet = &amp;new TelnetTestVersion($this);
$telnet-&gt;setReturnReference('_createSocket', $socket);<strong>
$telnet-&gt;expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
$telnet-&gt;Telnet();
$telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
...<strong>
$telnet-&gt;tally();</strong>
}
}
</pre>
</p>
<p>
<a class="target" name="moins">
<h2>Tester moins qu'une classe</h2>
</a>
</p>
<p>
Les m&eacute;thodes issues d'un objet fantaisie n'ont pas besoin d'&ecirc;tre des m&eacute;thodes fabrique, Il peut s'agir de n'importe quelle sorte de m&eacute;thode. Ainsi les objets fantaisie partiels nous permettent de prendre le contr&ocirc;le de n'importe quelle partie d'une classe, le constructeur except&eacute;. Nous pourrions m&ecirc;me aller jusqu'&agrave; cr&eacute;er des fantaisies sur toutes les m&eacute;thode &agrave; part celle que nous voulons effectivement tester.
</p>
<p>
Cette situation est assez hypoth&eacute;tique, &eacute;tant donn&eacute; que je ne l'ai jamais essay&eacute;e. Je suis ouvert &agrave; cette possibilit&eacute;, mais je crains qu'en for&ccedil;ant la granularit&eacute; d'un objet on n'obtienne pas forc&eacute;ment un code de meilleur qualit&eacute;. Personnellement j'utilise les objets fantaisie partiels comme moyen de passer outre la cr&eacute;ation ou alors de temps en temps pour tester le mod&egrave;le de conception TemplateMethod.
</p>
<p>
Pour choisir le m&eacute;canisme &agrave; utiliser, on en revient toujours aux standards de code de votre projet.
</p>
</div>
<div class="copyright">
Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004
</div>
</body>
</html>