From 322bb9cd2be9e51422cb2b82684692e825c2bfb7 Mon Sep 17 00:00:00 2001 From: brettp Date: Fri, 2 Oct 2009 18:40:04 +0000 Subject: Added simpletest and start of unit tests. git-svn-id: http://code.elgg.org/elgg/trunk@3503 36083f99-b078-4883-b0ff-0f9b5a30f544 --- .../docs/fr/partial_mocks_documentation.html | 460 +++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100755 vendors/simpletest/docs/fr/partial_mocks_documentation.html (limited to 'vendors/simpletest/docs/fr/partial_mocks_documentation.html') diff --git a/vendors/simpletest/docs/fr/partial_mocks_documentation.html b/vendors/simpletest/docs/fr/partial_mocks_documentation.html new file mode 100755 index 000000000..32a39c760 --- /dev/null +++ b/vendors/simpletest/docs/fr/partial_mocks_documentation.html @@ -0,0 +1,460 @@ + + + +Documentation SimpleTest : les objets fantaisie partiels + + + + +

Documentation sur les objets fantaisie partiels

+ This page... + +
+ +

+ 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à. +

+ +

Le problème de l'injection dans un objet fantaisie

+

+ 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. +

+

+ 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... +

+<?php
+require_once('socket.php');
+
+class Telnet {
+    ...
+    function &connect($ip, $port, $username, $password) {
+        $socket = &new Socket($ip, $port);
+        $socket->read( ... );
+        ...
+    }
+}
+?>
+
+ Nous voudrions vraiment avoir une version fantaisie + de l'objet socket, que pouvons nous faire ? +

+

+ 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. +

+

+ Voici ce que ça devrait être... +

+<?php
+require_once('socket.php');
+
+class Telnet {
+    ...
+    function &connect(&$socket, $username, $password) {
+        $socket->read( ... );
+        ...
+    }
+}
+?>
+
+ Sous-entendu, votre code de test est typique d'un cas + de test avec un objet fantaisie. +
+class TelnetTest extends UnitTestCase {
+    ...
+    function testConnection() {
+        $socket = &new MockSocket($this);
+        ...
+        $telnet = &new Telnet();
+        $telnet->connect($socket, 'Me', 'Secret');
+        ...
+    }
+}
+
+ 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. +

+

+ La solution suivante est de passer l'objet créé sous la forme + d'un paramètre optionnel... +

+<?php
+require_once('socket.php');
+
+class Telnet {
+    ...
+    function &connect($ip, $port, $username, $password, $socket = false) {
+        if (!$socket) {
+            $socket = &new Socket($ip, $port);
+        }
+        $socket->read( ... );
+        ...
+        return $socket;
+    }
+}
+?>
+
+ 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... +
+class TelnetTest extends UnitTestCase {
+    ...
+    function testConnection() {
+        $socket = &new MockSocket($this);
+        ...
+        $telnet = &new Telnet();
+        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket);
+        ...
+    }
+}
+
+ 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. +

+

+ Une autre solution encore est de laisser un objet fabrique + s'occuper de la création... +

+<?php
+require_once('socket.php');
+
+class Telnet {
+    function Telnet(&$network) {
+        $this->_network = &$network;
+    }
+    ...
+    function &connect($ip, $port, $username, $password) {
+        $socket = &$this->_network->createSocket($ip, $port);
+        $socket->read( ... );
+        ...
+        return $socket;
+    }
+}
+?>
+
+ 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... +
+class TelnetTest extends UnitTestCase {
+    ...
+    function testConnection() {
+        $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');
+        ...
+    }
+}
+
+ 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. +

+

+ Peut-on trouver un juste milieu ? +

+ +

Méthode fabrique protégée

+

+ 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... +

+<?php
+require_once('socket.php');
+
+class Telnet {
+    ...
+    function &connect($ip, $port, $username, $password) {
+        $socket = &$this->_createSocket($ip, $port);
+        $socket->read( ... );
+        ...
+    }
+    
+    function &_createSocket($ip, $port) {
+        return new Socket($ip, $port);
+    }
+}
+?>
+
+ Il s'agit là de la seule modification dans le code de l'application. +

+

+ Pour le scénario de test, nous devons créer + une sous-classe de manière à intercepter la création de la socket... +

+class TelnetTestVersion extends Telnet {
+    var $_mock;
+    
+    function TelnetTestVersion(&$mock) {
+        $this->_mock = &$mock;
+        $this->Telnet();
+    }
+    
+    function &_createSocket() {
+        return $this->_mock;
+    }
+}
+
+ 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 + connect(). + Autrement il pourrait donner un valeur nulle à partir de + _createSocket(). +

+

+ 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... +

+class TelnetTest extends UnitTestCase {
+    ...
+    function testConnection() {
+        $socket = &new MockSocket($this);
+        ...
+        $telnet = &new TelnetTestVersion($socket);
+        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
+        ...
+    }
+}
+
+ 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 ? +

+ +

Un objet fantaisie partiel

+

+ 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. +

+

+ Voici donc une version avec objet fantaisie partiel du test... +

+Mock::generatePartial(
+        'Telnet',
+        'TelnetTestVersion',
+        array('_createSocket'));
+
+class TelnetTest extends UnitTestCase {
+    ...
+    function testConnection() {
+        $socket = &new MockSocket($this);
+        ...
+        $telnet = &new TelnetTestVersion($this);
+        $telnet->setReturnReference('_createSocket', $socket);
+        $telnet->Telnet();
+        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
+        ...
+    }
+}
+
+ 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 à generatePartial() + nécessite trois paramètres : la classe à sous classer, + le nom de la nouvelle classe et une liste des méthodes à simuler. +

+

+ 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. +

+

+ 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. +

+

+ 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... +

+class TelnetTest extends UnitTestCase {
+    ...
+    function testConnection() {
+        $socket = &new MockSocket($this);
+        ...
+        $telnet = &new TelnetTestVersion($this);
+        $telnet->setReturnReference('_createSocket', $socket);
+        $telnet->expectOnce('_createSocket', array('127.0.0.1', 21));
+        $telnet->Telnet();
+        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
+        ...
+        $telnet->tally();
+    }
+}
+
+

+ +

Tester moins qu'une classe

+

+ 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. +

+

+ 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. +

+

+ Pour choisir le mécanisme à utiliser, on en revient + toujours aux standards de code de votre projet. +

+ +
+ References and related information... + + + + + -- cgit v1.2.3