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/mock_objects_documentation.html | 778 +++++++++++++++++++++ 1 file changed, 778 insertions(+) create mode 100755 vendors/simpletest/docs/fr/mock_objects_documentation.html (limited to 'vendors/simpletest/docs/fr/mock_objects_documentation.html') diff --git a/vendors/simpletest/docs/fr/mock_objects_documentation.html b/vendors/simpletest/docs/fr/mock_objects_documentation.html new file mode 100755 index 000000000..c03ac611f --- /dev/null +++ b/vendors/simpletest/docs/fr/mock_objects_documentation.html @@ -0,0 +1,778 @@ + + + +Documentation SimpleTest : les objets fantaise + + + + +

Documentation sur les objets fantaisie

+ This page... + +
+

Que sont les objets fantaisie ?

+

+ Les objets fantaisie - ou "mock objects" en anglais - + ont deux rôles pendant un scénario de test : acteur et critique. +

+

+ Le comportement d'acteur est celui de simuler + des objets difficiles à initialiser ou trop consommateurs + en temps pendant un test. + Le cas classique est celui de la connexion à une base de données. + Mettre sur pied une base de données de test au lancement + de chaque test ralentirait considérablement les tests + et en plus exigerait l'installation d'un moteur + de base de données ainsi que des données sur la machine de test. + Si nous pouvons simuler la connexion + et renvoyer des données à notre guise + alors non seulement nous gagnons en pragmatisme + sur les tests mais en sus nous pouvons nourrir + notre base avec des données falsifiées + et voir comment il répond. Nous pouvons + simuler une base de données en suspens ou + d'autres cas extrêmes sans avoir à créer + une véritable panne de base de données. + En d'autres termes nous pouvons gagner + en contrôle sur l'environnement de test. +

+

+ Si les objets fantaisie ne se comportaient que comme + des acteurs alors on les connaîtrait sous le nom de + bouchons serveur. +

+

+ Cependant non seulement les objets fantaisie jouent + un rôle (en fournissant à la demande les valeurs requises) + mais en plus ils sont aussi sensibles aux messages qui + leur sont envoyés (par le biais d'attentes). + En posant les paramètres attendus d'une méthode + ils agissent comme des gardiens : + un appel sur eux doit être réalisé correctement. + Si les attentes ne sont pas atteintes ils nous épargnent + l'effort de l'écriture d'une assertion de test avec + échec en réalisant cette tâche à notre place. + Dans le cas d'une connexion à une base de données + imaginaire ils peuvent tester si la requête, disons SQL, + a bien été formé par l'objet qui utilise cette connexion. + Mettez-les sur pied avec des attentes assez précises + et vous verrez que vous n'aurez presque plus d'assertion à écrire manuellement. +

+ +

Créer des objets fantaisie

+

+ Comme pour la création des bouchons serveur, tout ce dont + nous avons besoin c'est d'un classe existante. + La fameuse connexion à une base de données qui ressemblerait à... +

+class DatabaseConnection {
+    function DatabaseConnection() {
+    }
+    
+    function query() {
+    }
+    
+    function selectQuery() {
+    }
+}
+
+ Cette classe n'a pas encore besoin d'être implémentée. + Pour en créer sa version fantaisie nous devons juste + inclure la librairie d'objet fantaisie puis lancer le générateur... +
+require_once('simpletest/unit_tester.php');
+require_once('simpletest/mock_objects.php');
+require_once('database_connection.php');
+
+Mock::generate('DatabaseConnection');
+
+ Ceci génère une classe clone appelée MockDatabaseConnection. + Nous pouvons désormais créer des instances de + cette nouvelle classe à l'intérieur même de notre scénario de test... +
+require_once('simpletest/unit_tester.php');
+require_once('simpletest/mock_objects.php');
+require_once('database_connection.php');
+
+Mock::generate('DatabaseConnection');
+
+class MyTestCase extends UnitTestCase {
+    
+    function testSomething() {
+        $connection = &new MockDatabaseConnection($this);
+    }
+}
+
+ Contrairement aux bouchons, le constructeur + d'une classe fantaisie a besoin d'une référence au scénario + de test pour pouvoir transmettre les succès + et les échecs pendant qu'il vérifie les attentes. + Concrètement ça veut dire que les objets fantaisie + ne peuvent être utilisés qu'au sein d'un scénario de test. + Malgré tout, cette puissance supplémentaire implique + que les bouchons ne sont que rarement utilisés + si des objets fantaisie sont disponibles. +

+ +

Objets fantaisie en action

+

+ La version fantaisie d'une classe contient + toutes les méthodes de l'originale. + De la sorte une opération comme + $connection->query() + est encore possible. + Tout comme avec les bouchons, nous pouvons remplacer + la valeur nulle renvoyée par défaut... +

+$connection->setReturnValue('query', 37);
+
+ Désormais à chaque appel de + $connection->query() + nous recevons comme résultat 37. + Tout comme avec les bouchons nous pouvons utiliser + des jokers et surcharger le paramètre joker. + Nous pouvons aussi ajouter des méthodes supplémentaires + à l'objet fantaisie lors de sa génération + et lui choisir un nom de classe qui lui soit propre... +
+Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions'));
+
+ Ici l'objet fantaisie se comportera comme + si setOptions() existait dans la classe originale. + C'est pratique si une classe a utilisé le mécanisme + overload() de PHP pour ajouter des méthodes dynamiques. + Vous pouvez créer des fantaisies spéciales pour simuler cette situation. +

+

+ Tous les modèles disponibles avec les bouchons serveur + le sont également avec les objets fantaisie... +

+class Iterator {
+    function Iterator() {
+    }
+    
+    function next() {
+    }
+}
+
+ Une nouvelle fois, supposons que cet itérateur + ne retourne que du texte jusqu'au moment où il atteint + son terme, quand il renvoie false. + Nous pouvons le simuler avec... +
+Mock::generate('Iterator');
+
+class IteratorTest extends UnitTestCase() {
+    
+    function testASequence() {
+        $iterator = &new MockIterator($this);
+        $iterator->setReturnValue('next', false);
+        $iterator->setReturnValueAt(0, 'next', 'First string');
+        $iterator->setReturnValueAt(1, 'next', 'Second string');
+        ...
+    }
+}
+
+ Au moment du premier appel à next() + sur l'itérateur fantaisie il renverra tout d'abord + "First string", puis ce sera au tour de + "Second string" au deuxième appel + et ensuite pour tout appel suivant false + sera renvoyé. + Ces valeurs renvoyées successivement sont prioritaires + sur la valeur constante retournée. + Cette dernière est un genre de valeur par défaut si vous voulez. +

+

+ Reprenons aussi le conteneur d'information bouchonné + avec des pairs clef / valeur... +

+class Configuration {
+    function Configuration() {
+    }
+    
+    function getValue($key) {
+    }
+}
+
+ Il s'agit là d'une situation classique + d'utilisation d'objets fantaisie étant donné + que la configuration peut varier grandement de machine à machine : + ça contraint fortement la fiabilité de nos tests + si nous l'utilisons directement. + Le problème est que toutes les données nous parviennent + à travers la méthode getValue() + et que nous voulons des résultats différents pour des clefs différentes. + Heureusement les objets fantaisie ont un système de filtrage... +
+$config = &new MockConfiguration($this);
+$config->setReturnValue('getValue', 'primary', array('db_host'));
+$config->setReturnValue('getValue', 'admin', array('db_user'));
+$config->setReturnValue('getValue', 'secret', array('db_password'));
+
+ Le paramètre en plus est une liste d'arguments + à faire correspondre. Dans ce cas nous essayons + de faire correspondre un unique argument : + en l'occurrence la clef recherchée. + Maintenant que la méthode getValue() + est invoquée sur l'objet fantaisie... +
+$config->getValue('db_user')
+
+ ...elle renverra "admin". + Elle le trouve en essayant de faire correspondre + les arguments entrants dans sa liste + d'arguments sortants les uns après les autres + jusqu'au moment où une correspondance exacte est atteinte. +

+

+ Il y a des fois où vous souhaitez + qu'un objet spécifique soit servi par la fantaisie + plutôt qu'une copie. + De nouveau c'est identique au mécanisme des bouchons serveur... +

+class Thing {
+}
+
+class Vector {
+    function Vector() {
+    }
+    
+    function get($index) {
+    }
+}
+
+ Dans ce cas vous pouvez placer une référence + dans la liste renvoyée par l'objet fantaisie... +
+$thing = new Thing();
+$vector = &new MockVector($this);
+$vector->setReturnReference('get', $thing, array(12));
+
+ Avec cet arrangement vous savez qu'à chaque appel + de $vector->get(12) + le même $thing sera renvoyé. +

+ +

Objets fantaisie en critique

+

+ Même si les bouchons serveur vous isolent + du désordre du monde réel, il ne s'agit là que + de la moitié du bénéfice potentiel. + Vous pouvez avoir une classe de test recevant + les messages ad hoc, mais est-ce que votre nouvelle classe + renvoie bien les bons ? + Le tester peut devenir cafouillis sans une librairie d'objets fantaisie. +

+

+ Pour l'exemple, prenons une classe SessionPool + à laquelle nous allons ajouter une fonction de log. + Plutôt que de complexifier la classe originale, + nous souhaitons ajouter ce comportement avec un décorateur (GOF). + Pour l'instant le code de SessionPool ressemble à... +

+class SessionPool {
+    function SessionPool() {
+        ...
+    }
+    
+    function &findSession($cookie) {
+        ...
+    }
+    ...
+}
+
+class Session {
+    ...
+}
+
+
+ Alors que pour notre code de log, nous avons... +

+class Log {
+    function Log() {
+        ...
+    }
+    
+    function message() {
+        ...
+    }
+}
+
+class LoggingSessionPool {
+    function LoggingSessionPool(&$session_pool, &$log) {
+        ...
+    }
+    
+    function &findSession($cookie) {
+        ...
+    }
+    ...
+}
+
+ Dans tout ceci, la seule classe à tester est + LoggingSessionPool. En particulier, + nous voulons vérifier que la méthode findSession() + est appelée avec le bon identifiant de session au sein du cookie + et qu'elle renvoie bien le message "Starting session $cookie" + au loggueur. +

+

+ Bien que nous ne testions que quelques lignes + de code de production, voici la liste des choses + à faire dans un scénario de test conventionnel : +

    +
  1. Créer un objet de log.
  2. +
  3. Indiquer le répertoire d'écriture du fichier de log.
  4. +
  5. Modifier les droits sur le répertoire pour pouvoir y écrire le fichier.
  6. +
  7. Créer un objet SessionPool.
  8. +
  9. Lancer une session, ce qui demande probablement pas mal de choses.
  10. +
  11. Invoquer findSession().
  12. +
  13. Lire le nouvel identifiant de session (en espérant qu'il existe un accesseur !).
  14. +
  15. Lever une assertion de test pour vérifier que cet identifiant correspond bien au cookie.
  16. +
  17. Lire la dernière ligne du fichier de log.
  18. +
  19. Supprimer avec une (ou plusieurs) expression rationnelle les timestamps de log en trop, etc.
  20. +
  21. Vérifier que le message de session est bien dans le texte.
  22. +
+ Pas étonnant que les développeurs détestent + écrire des tests quand ils sont aussi ingrats. + Pour rendre les choses encore pire, à chaque fois que + le format de log change ou bien que la méthode de création + des sessions change, nous devons réécrire une partie + des tests alors même qu'ils ne testent pas ces parties + du système. Nous sommes en train de préparer + le cauchemar pour les développeurs de ces autres classes. +

+

+ A la place, voici la méthode complète pour le test + avec un peu de magie via les objets fantaisie... +

+Mock::generate('Session');
+Mock::generate('SessionPool');
+Mock::generate('Log');
+
+class LoggingSessionPoolTest extends UnitTestCase {
+    ...
+    function testFindSessionLogging() {
+        $session = &new MockSession($this);
+        $pool = &new MockSessionPool($this);
+        $pool->setReturnReference('findSession', $session);
+        $pool->expectOnce('findSession', array('abc'));
+        
+        $log = &new MockLog($this);
+        $log->expectOnce('message', array('Starting session abc'));
+        
+        $logging_pool = &new LoggingSessionPool($pool, $log);
+        $this->assertReference($logging_pool->findSession('abc'), $session);
+        $pool->tally();
+        $log->tally();
+    }
+}
+
+ Commençons par écrire une session simulacre. + Pas la peine d'être trop pointilleux avec + celle-ci puisque la vérification de la session + désirée est effectuée ailleurs. Nous avons + juste besoin de vérifier qu'il s'agit de + la même que celle qui vient du groupe commun des sessions. +

+

+ findSession() est un méthode fabrique + dont la simulation est décrite plus haut. + Le point de départ vient avec le premier appel + expectOnce(). Cette ligne indique + qu'à chaque fois que findSession() + est invoqué sur l'objet fantaisie, il vérifiera + les arguments entrant. S'il ne reçoit + que la chaîne "abc" en tant qu'argument + alors un succès est envoyé au testeur unitaire, + sinon c'est un échec qui est généré. + Il s'agit là de la partie qui teste si nous avons bien + la bonne session. La liste des arguments suit + une format identique à celui qui précise les valeurs renvoyées. + Vous pouvez avoir des jokers et des séquences + et l'ordre de l'évaluation restera le même. +

+

+ Si l'appel n'est jamais effectué alors n'est généré + ni le succès, ni l'échec. Pour contourner cette limitation, + nous devons dire à l'objet fantaisie que le test est terminé : + il pourra alors décider si les attentes ont été répondues. + L'assertion du testeur unitaire de ceci est déclenchée + par l'appel tally() à la fin du test. +

+

+ Nous utilisons le même modèle pour mettre sur pied + le loggueur fantaisie. Nous lui indiquons que message() + devrait être invoqué une fois et une fois seulement + avec l'argument "Starting session abc". + En testant les arguments d'appel, plutôt que ceux de sortie du loggueur, + nous isolons le test de tout modification dans le loggueur. +

+

+ Nous commençons le lancement nos tests à la création + du nouveau LoggingSessionPool + et nous l'alimentons avec nos objets fantaisie juste créés. + Désormais tout est sous contrôle. Au final nous confirmons + que le $session donné au décorateur est bien + celui reçu et prions les objets fantaisie de lancer leurs + tests de comptage d'appel interne avec les appels tally(). +

+

+ Il y a encore pas mal de code de test, mais ce code est très strict. + S'il vous semble encore terrifiant il l'est bien moins + que si nous avions essayé sans les objets fantaisie + et ce test en particulier, interactions plutôt que résultat, + est toujours plus difficile à mettre en place. + Le plus souvent vous aurez besoin de tester des situations + plus complexes sans ce niveau ni cette précision. + En outre une partie peut être remaniée avec la méthode + de scénario de test setUp(). +

+

+ Voici la liste complète des attentes que vous pouvez + placer sur un objet fantaisie avec + SimpleTest... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttenteNécessite tally() +
expectArguments($method, $args)Non
expectArgumentsAt($timing, $method, $args)Non
expectCallCount($method, $count)Oui
expectMaximumCallCount($method, $count)Non
expectMinimumCallCount($method, $count)Oui
expectNever($method)Non
expectOnce($method, $args)Oui
expectAtLeastOnce($method, $args)Oui
+ Où les paramètres sont... +

+
$method
+
Le nom de la méthode, sous la forme d'une chaîne, + à laquelle la condition doit être appliquée.
+
$args
+
+ Les arguments sous la forme d'une liste. + Les jokers peuvent être inclus de la même manière + qu'avec setReturn(). + Cet argument est optionnel pour expectOnce() + et expectAtLeastOnce(). +
+
$timing
+
+ Le seul point dans le temps pour tester + la condition. Le premier appel commence à zéro. +
+
$count
+
Le nombre d'appels attendu.
+
+ La méthode expectMaximumCallCount() + est légèrement différente dans le sens où elle ne pourra + générer qu'un échec. Elle reste silencieuse + si la limite n'est jamais atteinte. +

+

+ Par ailleurs si vous avez just un appel dans votre test, + vérifiez bien que vous utiliser + expectOnce.
+ Utiliser $mocked->expectAt(0, 'method', 'args); + tout seul ne sera pas pris en compte : + la vérification des arguments et le comptage total + sont pour l'instant encore indépendant. +

+

+ Comme avec les assertions dans les scénarios de test, + toutes ces attentes peuvent accepter une surcharge de + message sous la forme d'un paramètre supplémentaire. + Par ailleurs le message d'échec original peut être inclus + dans le résultat avec "%s". +

+ +

D'autres approches

+

+ Il existe trois approches pour créer des objets + fantaisie en comprenant celle utilisée par SimpleTest. + Les coder à la main en utilisant une classe de base, + les générer dans un fichier ou les générer dynamiquement à la volée. +

+

+ Les objets fantaisie générés avec + SimpleTest sont dynamiques. + Ils sont créés à l'exécution dans la mémoire, + grâce à eval(), plutôt qu'écrits dans un fichier. + Cette opération les rend facile à créer, + en une seule ligne, surtout par rapport à leur création + à la main dans une hiérarchie de classe parallèle. + Le problème avec ce comportement tient généralement + dans la mise en place des tests proprement dits. + Si les objets originaux changent les versions fantaisie + sur lesquels reposent les tests, une désynchronisation peut subvenir. + Cela peut aussi arriver avec l'approche en hiérarchie parallèle, + mais c'est détecté beaucoup plus vite. +

+

+ Bien sûr, la solution est d'ajouter de véritables tests d'intégration. + Vous n'en avez pas besoin de beaucoup + et le côté pratique des objets fantaisie fait plus + que compenser la petite dose de test supplémentaire. + Vous ne pouvez pas avoir confiance dans du code qui + ne serait testé que par des objets fantaisie. +

+

+ Si vous restez déterminé de construire des librairies + statiques d'objets fantaisie parce que vous souhaitez + émuler un comportement très spécifique, + vous pouvez y parvenir grâce au générateur de classe de SimpleTest. + Dans votre fichier librairie, par exemple + mocks/connection.php pour une connexion à une base de données, + créer un objet fantaisie et provoquer l'héritage + pour hériter pour surcharger des méthodes spéciales + ou ajouter des préréglages... +

+<?php
+    require_once('simpletest/mock_objects.php');
+    require_once('../classes/connection.php');
+
+    Mock::generate('Connection', 'BasicMockConnection');
+    class MockConnection extends BasicMockConnection {
+        function MockConnection(&$test, $wildcard = '*') {
+            $this->BasicMockConnection($test, $wildcard);
+            $this->setReturn('query', false);
+        }
+    }
+?>
+
+ L'appel generate dit au générateur de classe + d'en créer une appelée BasicMockConnection + plutôt que la plus courante MockConnection. + Ensuite nous héritons à partir de celle-ci pour obtenir + notre version de MockConnection. + En interceptant de cette manière nous pouvons ajouter + un comportement, ici transformer la valeur par défaut de + query() en "false". + En utilisant le nom par défaut nous garantissons + que le générateur de classe fantaisie n'en recréera + pas une autre différente si il est invoqué ailleurs + dans les tests. Il ne créera jamais de classe + si elle existe déjà. Aussi longtemps que le fichier + ci-dessus est inclus avant alors tous les tests qui + généraient MockConnection devraient + utiliser notre version à présent. Par contre si + nous avons une erreur dans l'ordre et que la librairie + de fantaisie en crée une d'abord alors la création + de la classe échouera tout simplement. +

+

+ Utiliser cette astuce si vous vous trouvez avec beaucoup + de comportement en commun sur les objets fantaisie + ou si vous avez de fréquents problèmes d'intégration + plus tard dans les étapes de test. +

+ +

Je pense que SimpleTest pue !

+

+ Mais au moment d'écrire ces lignes c'est le seul + à gérer les objets fantaisie, donc vous êtes bloqué avec lui ? +

+

+ Non, pas du tout. + SimpleTest est une boîte à outils + et parmi ceux-ci on trouve les objets fantaisie + qui peuvent être utilisés indépendamment. + Supposons que vous avez votre propre testeur unitaire favori + et que tous vos tests actuels l'utilisent. + Prétendez que vous avez appelé votre tester unitaire PHPUnit + (c'est ce que tout le monde a fait) et que la classe principale + de test ressemble à... +

+class PHPUnit {
+    function PHPUnit() {
+    }
+    
+    function assertion($message, $assertion) {
+    }
+    ...
+}
+
+ La seule chose que la méthode assertion() réalise, + c'est de préparer une sortie embellie alors le paramètre boolien + de l'assertion sert à déterminer s'il s'agit d'une erreur ou d'un succès. + Supposons qu'elle est utilisée de la manière suivante... +
+$unit_test = new PHPUnit();
+$unit_test>assertion('I hope this file exists', file_exists('my_file'));
+
+ Comment utiliser les objets fantaisie avec ceci ? +

+

+ Il y a une méthode protégée sur la classe de base + des objets fantaisie : elle s'appelle _assertTrue(). + En surchargeant cette méthode nous pouvons utiliser + notre propre format d'assertion. + Nous commençons avec une sous-classe, dans my_mock.php... +

+<?php
+    require_once('simpletest/mock_objects.php');
+    
+    class MyMock extends SimpleMock() {
+        function MyMock(&$test, $wildcard) {
+            $this->SimpleMock($test, $wildcard);
+        }
+        
+        function _assertTrue($assertion, $message) {
+            $test = &$this->getTest();
+            $test->assertion($message, $assertion);
+        }
+    }
+?>
+
+ Maintenant une instance de MyMock + créera un objet qui parle le même langage que votre testeur. + Bien sûr le truc c'est que nous créons jamais un tel objet : + le générateur s'en chargera. Nous avons juste besoin + d'une ligne de code supplémentaire pour dire au générateur + d'utiliser vos nouveaux objets fantaisie... +
+<?php
+    require_once('simpletst/mock_objects.php');
+    
+    class MyMock extends SimpleMock() {
+        function MyMock($test, $wildcard) {
+            $this->SimpleMock(&$test, $wildcard);
+        }
+        
+        function _assertTrue($assertion, $message , &$test) {
+            $test->assertion($message, $assertion);
+        }
+    }
+    SimpleTestOptions::setMockBaseClass('MyMock');
+?>
+
+ A partir de maintenant vous avez juste à inclure + my_mock.php à la place de la version par défaut + simple_mock.php et vous pouvez introduire + des objets fantaisie dans votre suite de tests existants. +

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