delete, select & insert upgraded More utility function (node to array, arrayToNode) XMLDB special move command PHP Unit Test
		
			
				
	
	
		
			461 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			18 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">
 | 
						|
<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>
 |