More SQL command (drop table, drop database)
delete, select & insert upgraded More utility function (node to array, arrayToNode) XMLDB special move command PHP Unit Test
This commit is contained in:
460
simpletest/docs/fr/partial_mocks_documentation.html
Normal file
460
simpletest/docs/fr/partial_mocks_documentation.html
Normal file
@@ -0,0 +1,460 @@
|
||||
<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">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Documentation sur les objets fantaisie partiels</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#injection">Le problème de l'injection d'un objet fantaisie</a>.
|
||||
</li>
|
||||
<li>
|
||||
Déplacer la création vers une méthode <a href="#creation">fabrique protégée</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#partiel">L'objet fantaisie partiel</a> génère une sous-classe.
|
||||
</li>
|
||||
<li>
|
||||
Les objets fantaisie partiels <a href="#moins">testent moins qu'une classe</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
Un objet fantaisie partiel n'est ni plus ni moins
|
||||
qu'un modèle de conception pour soulager un problème spécifique
|
||||
du test avec des objets fantaisie, celui de placer
|
||||
des objets fantaisie dans des coins serrés.
|
||||
Il s'agit d'un outil assez limité et peut-être même
|
||||
une idée pas si bonne que ça. Elle est incluse dans SimpleTest
|
||||
pour la simple raison que je l'ai trouvée utile
|
||||
à plus d'une occasion et qu'elle m'a épargnée
|
||||
pas mal de travail dans ces moments-là.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="injection"><h2>Le problème de l'injection dans un objet fantaisie</h2></a></p>
|
||||
<p>
|
||||
Quand un objet en utilise un autre il est très simple
|
||||
d'y faire circuler une version fantaisie déjà prête
|
||||
avec ses attentes. Les choses deviennent un peu plus délicates
|
||||
si un objet en crée un autre et que le créateur est celui
|
||||
que l'on souhaite tester. Cela revient à dire que l'objet
|
||||
créé devrait être une fantaisie, mais nous pouvons
|
||||
difficilement dire à notre classe sous test de créer
|
||||
un objet fantaisie plutôt qu'un "vrai" objet.
|
||||
La classe testée ne sait mê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éer une socket réseau pour envoyer ses messages.
|
||||
La méthode de connexion pourrait ressemble à quelque chose comme...
|
||||
<pre>
|
||||
<strong><?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {
|
||||
$socket = &new Socket($ip, $port);
|
||||
$socket->read( ... );
|
||||
...
|
||||
}
|
||||
}
|
||||
?></strong>
|
||||
</pre>
|
||||
Nous voudrions vraiment avoir une version fantaisie
|
||||
de l'objet socket, que pouvons nous faire ?
|
||||
</p>
|
||||
<p>
|
||||
La première solution est de passer la socket en
|
||||
tant que paramètre, ce qui force la création
|
||||
au niveau inférieur. Charger le client de cette tâche
|
||||
est effectivement une bonne approche si c'est possible
|
||||
et devrait conduire à un remaniement -- de la création
|
||||
à partir de l'action. En fait, c'est là une des manières
|
||||
avec lesquels tester en s'appuyant sur des objets fantaisie
|
||||
vous force à coder des solutions plus resserrées sur leur objectif.
|
||||
Ils améliorent votre programmation.
|
||||
</p>
|
||||
<p>
|
||||
Voici ce que ça devrait être...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
<strong>function &connect(&$socket, $username, $password) {
|
||||
$socket->read( ... );
|
||||
...
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</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 = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new Telnet();
|
||||
$telnet->connect($socket, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
C'est assez évident que vous ne pouvez descendre que d'un niveau.
|
||||
Vous ne voudriez pas que votre application de haut niveau
|
||||
crée tous les fichiers de bas niveau, sockets et autres connexions
|
||||
à la base de données dont elle aurait besoin.
|
||||
Elle ne connaîtrait pas les paramètres du constructeur de toute façon.
|
||||
</p>
|
||||
<p>
|
||||
La solution suivante est de passer l'objet créé sous la forme
|
||||
d'un paramètre optionnel...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...<strong>
|
||||
function &connect($ip, $port, $username, $password, $socket = false) {
|
||||
if (!$socket) {
|
||||
$socket = &new Socket($ip, $port);
|
||||
}
|
||||
$socket->read( ... );</strong>
|
||||
...
|
||||
return $socket;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
Pour une solution rapide, c'est généralement suffisant.
|
||||
Ensuite le test est très similaire : comme si le paramètre
|
||||
était transmis formellement...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket);
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Le problème de cette approche tient dans son manque de netteté.
|
||||
Il y a du code de test dans la classe principale et aussi
|
||||
des paramètres transmis dans le scénario de test
|
||||
qui ne sont jamais utilisés. Il s'agit là 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éation...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {<strong>
|
||||
function Telnet(&$network) {
|
||||
$this->_network = &$network;
|
||||
}</strong>
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {<strong>
|
||||
$socket = &$this->_network->createSocket($ip, $port);
|
||||
$socket->read( ... );</strong>
|
||||
...
|
||||
return $socket;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
Il s'agit là probablement de la réponse la plus travaillée
|
||||
étant donné que la création est maintenant située
|
||||
dans une petite classe spécialisée. La fabrique réseau
|
||||
peut être testée séparément et utilisée en tant que fantaisie
|
||||
quand nous testons la classe telnet...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$network = &new MockNetwork($this);
|
||||
$network->setReturnReference('createSocket', $socket);
|
||||
$telnet = &new Telnet($network);
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Le problème reste que nous ajoutons beaucoup de classes
|
||||
à la bibliothè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éthode fabrique protégée</h2></a></p>
|
||||
<p>
|
||||
Il existe une technique pour palier à ce problème
|
||||
sans créer de nouvelle classe dans l'application;
|
||||
par contre elle induit la création d'une sous-classe au moment du test.
|
||||
Premièrement nous déplaçons la création de la socket dans sa propre méthode...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {<strong>
|
||||
$socket = &$this->_createSocket($ip, $port);</strong>
|
||||
$socket->read( ... );
|
||||
...
|
||||
}<strong>
|
||||
|
||||
function &_createSocket($ip, $port) {
|
||||
return new Socket($ip, $port);
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
Il s'agit là de la seule modification dans le code de l'application.
|
||||
</p>
|
||||
<p>
|
||||
Pour le scénario de test, nous devons créer
|
||||
une sous-classe de manière à intercepter la création de la socket...
|
||||
<pre>
|
||||
<strong>class TelnetTestVersion extends Telnet {
|
||||
var $_mock;
|
||||
|
||||
function TelnetTestVersion(&$mock) {
|
||||
$this->_mock = &$mock;
|
||||
$this->Telnet();
|
||||
}
|
||||
|
||||
function &_createSocket() {
|
||||
return $this->_mock;
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
Ici j'ai déplacé la fantaisie dans le constructeur,
|
||||
mais un setter aurait fonctionné tout aussi bien.
|
||||
Notez bien que la fantaisie est placée dans une variable
|
||||
d'objet avant que le constructeur ne soit attaché.
|
||||
C'est nécessaire dans le cas où le constructeur appelle
|
||||
<span class="new_code">connect()</span>.
|
||||
Autrement il pourrait donner un valeur nulle à partir de
|
||||
<span class="new_code">_createSocket()</span>.
|
||||
</p>
|
||||
<p>
|
||||
Après la réalisation de tout ce travail supplémentaire
|
||||
le scénario de test est assez simple.
|
||||
Nous avons juste besoin de tester notre nouvelle classe à la place...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($socket);
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Cette nouvelle classe est très simple bien sûr.
|
||||
Elle ne fait qu'initier une valeur renvoyée, à la manière
|
||||
d'une fantaisie. Ce serait pas mal non plus si elle pouvait
|
||||
vérifier les paramètres entrants.
|
||||
Exactement comme un objet fantaisie.
|
||||
Il se pourrait bien que nous ayons à réaliser cette astuce régulièrement :
|
||||
serait-il possible d'automatiser la création de cette sous-classe ?
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="partiel"><h2>Un objet fantaisie partiel</h2></a></p>
|
||||
<p>
|
||||
Bien sûr la réponse est "oui"
|
||||
ou alors j'aurais arrêté d'écrire depuis quelques temps déjà !
|
||||
Le test précédent a représenté beaucoup de travail,
|
||||
mais nous pouvons générer la sous-classe en utilisant
|
||||
une approche à 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 = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($this);
|
||||
$telnet->setReturnReference('_createSocket', $socket);
|
||||
$telnet->Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
La fantaisie partielle est une sous-classe de l'original
|
||||
dont on aurait "remplacé" les méthodes sélectionnées
|
||||
avec des versions de test. L'appel à <span class="new_code">generatePartial()</span>
|
||||
nécessite trois paramètres : la classe à sous classer,
|
||||
le nom de la nouvelle classe et une liste des méthodes à simuler.
|
||||
</p>
|
||||
<p>
|
||||
Instancier les objets qui en résultent est plutôt délicat.
|
||||
L'unique paramètre du constructeur d'un objet fantaisie partiel
|
||||
est la référence du testeur unitaire.
|
||||
Comme avec les objets fantaisie classiques c'est nécessaire
|
||||
pour l'envoi des résultats de test en réponse à la vérification des attentes.
|
||||
</p>
|
||||
<p>
|
||||
Une nouvelle fois le constructeur original n'est pas lancé.
|
||||
Indispensable dans le cas où le constructeur aurait besoin
|
||||
des méthodes fantaisie : elles n'ont pas encore été initiées !
|
||||
Nous initions les valeurs retournées à cet instant et
|
||||
ensuite lançons le constructeur avec ses paramètres normaux.
|
||||
Cette construction en trois étapes de "new",
|
||||
suivie par la mise en place des mé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éthodes
|
||||
fantaisie ont les mêmes fonctionnalités que dans
|
||||
le cas des objets fantaisie et toutes les méthodes
|
||||
non fantaisie se comportent comme avant.
|
||||
Nous pouvons mettre en place des attentes très facilement...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($this);
|
||||
$telnet->setReturnReference('_createSocket', $socket);<strong>
|
||||
$telnet->expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
|
||||
$telnet->Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...<strong>
|
||||
$telnet->tally();</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="moins"><h2>Tester moins qu'une classe</h2></a></p>
|
||||
<p>
|
||||
Les méthodes issues d'un objet fantaisie n'ont pas
|
||||
besoin d'être des méthodes fabrique, Il peut s'agir
|
||||
de n'importe quelle sorte de méthode.
|
||||
Ainsi les objets fantaisie partiels nous permettent
|
||||
de prendre le contrôle de n'importe quelle partie d'une classe,
|
||||
le constructeur excepté. Nous pourrions même aller jusqu'à
|
||||
créer des fantaisies sur toutes les méthodes à part celle
|
||||
que nous voulons effectivement tester.
|
||||
</p>
|
||||
<p>
|
||||
Cette situation est assez hypothétique, étant donné
|
||||
que je ne l'ai jamais essayée. Je suis ouvert à cette possibilité,
|
||||
mais je crains qu'en forçant la granularité d'un objet
|
||||
on n'obtienne pas forcément un code de meilleur qualité.
|
||||
Personnellement j'utilise les objets fantaisie partiels
|
||||
comme moyen de passer outre la création ou alors
|
||||
de temps en temps pour tester le modèle de conception TemplateMethod.
|
||||
</p>
|
||||
<p>
|
||||
Pour choisir le mécanisme à utiliser, on en revient
|
||||
toujours aux standards de code de votre projet.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
La page du projet SimpleTest sur
|
||||
<a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://simpletest.org/api/">L'API complète pour SimpleTest</a>
|
||||
à partir de PHPDoc.
|
||||
</li>
|
||||
<li>
|
||||
La méthode fabrique protégée est décrite dans
|
||||
<a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">
|
||||
cet article d'IBM</a>. Il s'agit de l'unique papier
|
||||
formel que j'ai vu sur ce problème.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user