diff options
author | Robin Appelman <icewind@owncloud.com> | 2012-02-16 09:44:49 +0100 |
---|---|---|
committer | Robin Appelman <icewind@owncloud.com> | 2012-02-16 09:44:49 +0100 |
commit | 20553c1afe035b2e020409fd516d0288b748dc7f (patch) | |
tree | 9b7cc32ee2560d86315339b9716c1a790a680afc /3rdparty/simpletest/docs/fr/mock_objects_documentation.html | |
parent | 19827b8b35de6192d54bf7600e9e29800c93b21b (diff) | |
download | nextcloud-server-20553c1afe035b2e020409fd516d0288b748dc7f.tar.gz nextcloud-server-20553c1afe035b2e020409fd516d0288b748dc7f.zip |
Revert "remove the 3rdparty files. everything is now in https://gitorious.org/owncloud/3rdparty"
This reverts commit dccdeca2581f705c69eb4266aa646173f588a9de.
Diffstat (limited to '3rdparty/simpletest/docs/fr/mock_objects_documentation.html')
-rw-r--r-- | 3rdparty/simpletest/docs/fr/mock_objects_documentation.html | 933 |
1 files changed, 933 insertions, 0 deletions
diff --git a/3rdparty/simpletest/docs/fr/mock_objects_documentation.html b/3rdparty/simpletest/docs/fr/mock_objects_documentation.html new file mode 100644 index 00000000000..97e2c86ee73 --- /dev/null +++ b/3rdparty/simpletest/docs/fr/mock_objects_documentation.html @@ -0,0 +1,933 @@ +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>Documentation SimpleTest : les objets fantaise</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</h1> + This page... + <ul> +<li> + <a href="#what">Que sont les objets fantaisie ?</a> + </li> +<li> + <a href="#creation">Créer des objets fantaisie</a>. + </li> +<li> + <a href="#expectations">Les objets fantaisie en tant que critiques</a> avec les attentes. + </li> +</ul> +<div class="content"> + <h2> +<a class="target" name="what"></a>Que sont les objets fantaisie ?</h2> + <p> + Les objets fantaisie - ou "mock objects" en anglais - + ont deux rôles pendant un scénario de test : acteur et critique. + </p> + <p> + 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. + </p> + <p> + Si les objets fantaisie ne se comportaient que comme + des acteurs alors on les connaîtrait sous le nom de + <a href="server_stubs_documentation.html">bouchons serveur</a>. + Il s'agissait originairement d'un patron de conception + identifié par Robert Binder (<a href="">Testing + object-oriented systems</a>: models, patterns, and tools, + Addison-Wesley) en 1999. + </p> + <p> + Un bouchon serveur est une simulation d'un objet ou d'un composant. + Il doit remplacer exactement un composant dans un système + en vue d'effectuer des tests ou un prototypage, + tout en restant ultra-léger. + Il permet aux tests de s'exécuter plus rapidement, ou + si la classe simulée n'a pas encore été écrite, + de se lancer tout court. + </p> + <p> + 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. + </p> + <p> + 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. + </p> + + <h2> +<a class="target" name="creation"></a>Créer des objets fantaisie</h2> + <p> + Tout ce dont nous avons besoin est une classe existante ou une interface, + par exemple une connexion à la base de données qui ressemblerait à... +<pre> +<strong>class DatabaseConnection { + function DatabaseConnection() { } + function query($sql) { } + function selectQuery($sql) { } +}</strong> +</pre> + Pour en créer sa version fantaisie nous devons juste + lancer le générateur... +<pre> +require_once('simpletest/autorun.php'); +require_once('database_connection.php'); + +<strong>Mock::generate('DatabaseConnection');</strong> +</pre> + Ce code génère une classe clone appelée <span class="new_code">MockDatabaseConnection</span>. + Cette nouvelle classe lui ressemble en tout point, + sauf qu'elle ne fait rien du tout. + </p> + <p> + Cette nouvelle classe est génératlement + une sous-classe de <span class="new_code">DatabaseConnection</span>. + Malheureusement, il n'y as aucun moyen de créer une version fantaisie + d'une classe avec une méthode <span class="new_code">final</span> sans avoir + une version fonctionnelle de cette méthode. + Ce n'est pas pas très satisfaisant. + Si la cible est une interface ou si les méthodes <span class="new_code">final</span> + existent dans la classe cible, alors une toute nouvelle classe + est créée, elle implémente juste les même interfaces. + Si vous essayer de faire passer cette classe à travers un indice de type + qui spécifie le véritable nom de l'ancienne classe, alors il échouera. + Du code qui forcerait un indice de type tout en utilisant + des méthodes <span class="new_code">final</span> ne pourrait probablement pas être + testé efficacement avec des objets fantaisie. + </p> + <p> + Si vous voulez voir le code généré, il suffit de faire un <span class="new_code">print</span> + de la sortie de <span class="new_code">Mock::generate()</span>. + VOici le code généré pour la classe <span class="new_code">DatabaseConnection</span> + à la place de son interface... +<pre> +class MockDatabaseConnection extends DatabaseConnection { + public $mock; + protected $mocked_methods = array('databaseconnection', 'query', 'selectquery'); + + function MockDatabaseConnection() { + $this->mock = new SimpleMock(); + $this->mock->disableExpectationNameChecks(); + } + ... + function DatabaseConnection() { + $args = func_get_args(); + $result = &$this->mock->invoke("DatabaseConnection", $args); + return $result; + } + function query($sql) { + $args = func_get_args(); + $result = &$this->mock->invoke("query", $args); + return $result; + } + function selectQuery($sql) { + $args = func_get_args(); + $result = &$this->mock->invoke("selectQuery", $args); + return $result; + } +} +</pre> + Votre sortie dépendra quelque peu de votre version précise de SimpleTest. + </p> + <p> + En plus des méthodes d'origine de la classe, vous en verrez d'autres + pour faciliter les tests. + Nous y reviendrons. + </p> + <p> + Nous pouvons désormais créer des instances de + cette nouvelle classe à l'intérieur même de notre scénario de test... +<pre> +require_once('simpletest/autorun.php'); +require_once('database_connection.php'); + +Mock::generate('DatabaseConnection'); + +class MyTestCase extends UnitTestCase { + + function testSomething() { + <strong>$connection = new MockDatabaseConnection();</strong> + } +} +</pre> + La version fantaisie contient toutles méthodes de l'originale. + En outre, tous les indices de type seront préservés. + Dison que nos méthodes de requête attend un objet <span class="new_code">Query</span>... +<pre> +<strong>class DatabaseConnection { + function DatabaseConnection() { } + function query(Query $query) { } + function selectQuery(Query $query) { } +}</strong> +</pre> + Si nous lui passons le mauvais type d'objet + ou même pire un non-objet... +<pre> +class MyTestCase extends UnitTestCase { + + function testSomething() { + $connection = new MockDatabaseConnection(); + $connection->query('insert into accounts () values ()'); + } +} +</pre> + ...alors le code renverra une violation de typage, + exactement comme on aurait pû s'y attendre + avec la classe d'origine. + </p> + <p> + Si la version fantaisie implémente bien toutes les méthodes + de l'originale, elle renvoit aussi systématiquement <span class="new_code">null</span>. + Et comme toutes les méthodes qui renvoient toujours <span class="new_code">null</span> + ne sont pas très utiles, nous avons besoin de leur faire dire + autre chose... + </p> + + <h2> +<a class="target" name="bouchon"></a>Objets fantaisie en action</h2> + <p> + Changer le <span class="new_code">null</span> renvoyé par la méthode fantaisie + en autre chose est assez facile... +<pre> +<strong>$connection->returns('query', 37)</strong> +</pre> + Désormais à chaque appel de <span class="new_code">$connection->query()</span> + nous obtenons un résultat de 37. + Il n'y a rien de particulier à cette valeur de 37. + Cette valeur de retour peut être aussi compliqué que nécessaire. + </p> + <p> + Ici les paramètres ne sont pas significatifs, + nous aurons systématiquement la même valeur en retour + une fois initialisés de la sorte. + Cela pourrait ne pas ressembler à une copie convaincante + de notre connexion à la base de données, mais pour + la demi-douzaine de lignes de code de notre méthode de test + c'est généralement largement assez. + </p> + <p> + Sauf que les choses ne sont pas toujours si simples. + Les itérateurs sont un problème récurrent, si par exemple + renvoyer systématiquement la même valeur entraine + une boucle infinie dans l'objet testé. + Pour ces cas-là, nous avons besoin d'une séquence de valeurs. + Supposons que nous avons un itérateur simple qui ressemble à... +<pre> +class Iterator { + function Iterator() { } + function next() { } +} +</pre> + Il s'agit là de l'itérateur le plus basique que nous puissions imaginer. + Supponsons que cet itérateur ne renvoit que du texte + jusqu'à ce qu'il atteigne la fin et qu'il renvoie alors un false, + nous pouvons le simuler avec... +<pre> +Mock::generate('Iterator'); + +class IteratorTest extends UnitTestCase() { + + function testASequence() {<strong> + $iterator = new MockIterator(); + $iterator->returns('next', false); + $iterator->returnsAt(0, 'next', 'First string'); + $iterator->returnsAt(1, 'next', 'Second string');</strong> + ... + } +} +</pre> + Quand <span class="new_code">next()</span> est appelé par le <span class="new_code">MockIterator</span> + il commencera par renvoyer "First string", + au deuxième passage "Second string" sera renvoyé + et sur n'importe quel autre appel <span class="new_code">false</span> sera renvoyé + à son tour. + Les valeurs renvoyées les unes après les autres auront la priorité + sur la valeur constante. + Cette dernière est la valeur par défaut en quelque sorte. + </p> + <p> + Une autre situation délicate serait une opération + <span class="new_code">get()</span> surchargée. + Un exemple serait un conteneur d'information avec des pairs clef/valeur. + Nous partons cette fois d'une classe de configuration telle... +<pre> +class Configuration { + function Configuration() { ... } + function get($key) { ... } +} +</pre> + C'est une situation courante pour utiliser des objets fantaisie, + étant donné que la véritable configuration sera différente + d'une machine à l'autre et parfois même d'un test à l'autre. + Cependant un problème apparaît quand toutes les données passent + par la méthode <span class="new_code">get()</span> et que nous souhaitons + quand même des résultats différents pour des clefs différentes. + Par chance les objets fantaisie ont un système de filtre... +<pre> +<strong>$config = &new MockConfiguration(); +$config->returns('get', 'primary', array('db_host')); +$config->returns('get', 'admin', array('db_user')); +$config->returns('get', 'secret', array('db_password'));</strong> +</pre> + Le dernier paramètre est une liste d'arguements + pour vérifier une correspondance. + Dans ce cas, nous essayons de faire correspondre un argument + qui se trouve être la clef de recherche. + Désormais quand l'objet fantaisie voit + sa méthode <span class="new_code">get()</span> invoquée... +<pre> +$config->get('db_user') +</pre> + ...il renverra "admin". + Il trouve cette valeur en essayant de faire correspondre + l'argument reçu à ceux de ses propres listes : dès qu'une + correspondance complète est trouvé, il s'arrête. + </p> + <p> + Vous pouvez préciser un argument par défaut via... +<pre><strong> +$config->returns('get', false, array('*'));</strong> +</pre> + Ce n'est pas la même chose que de définir la valeur renvoyée + sans aucun arguement... +<pre><strong> +$config->returns('get', false);</strong> +</pre> + Dans le premier cas, il acceptera n'importe quel argument, + mais il en faut au moins un. + Dans le deuxième cas, n'importe quel nombre d'arguments + fera l'affaire and il agit comme un attrape-tout après + toutes les autres vérifications. + Notez que si - dans le premier cas - nous ajoutons + d'autres options avec paramètre unique après le joker, + alors elle seront ignorés puisque le joker fera + la première correspondance. + Avec des listes complexes de paramètres, l'ordre devient + important au risque de voir la correspondance souhaitée + masqué par un joker apparu plus tôt. + Déclarez les plus spécifiques d'abord si vous n'êtes pas sûr. + </p> + <p> + Il y a des moments où vous souhaitez qu'une référence + bien spécifique soit servie par l'objet fantaisie plutôt + qu'une copie. + C'est plutôt rare pour dire le moins, mais vous pourriez + être en train de simuler un conteneur qui détiendrait + des primitives, tels des chaînes de caractères. + Par exemple. +<pre> +class Pad { + function Pad() { } + function &note($index) { } +} +</pre> + Dans ce cas, vous pouvez définir une référence dans la liste + des valeurs retournées par l'objet fantaisie... +<pre> +function testTaskReads() { + $note = 'Buy books'; + $pad = new MockPad(); + $vector-><strong>returnsByReference(</strong>'note', $note, array(3)<strong>)</strong>; + $task = new Task($pad); + ... +} +</pre> + Avec cet assemblage vous savez qu'à chaque fois + que <span class="new_code">$pad->note(3)</span> est appelé + il renverra toujours la même <span class="new_code">$note</span>, + même si celle-ci est modifiée. + </p> + <p> + Ces trois facteurs, timing, paramètres et références, + peuvent être combinés orthogonalement. + Par exemple... +<pre> +$buy_books = 'Buy books'; +$write_code = 'Write code'; +$pad = new MockPad(); +$vector-><strong>returnsByReferenceAt(0, 'note', $buy_books, array('*', 3));</strong> +$vector-><strong>returnsByReferenceAt(1, 'note', $write_code, array('*', 3));</strong> +</pre> + Cela renverra une référence à <span class="new_code">$buy_books</span> et + ensuite à <span class="new_code">$write_code</span>, mais seuleent si deux paramètres + sont utilisés, le deuxième devant être l'entier 3. + Cela devrait couvrir la plupart des situations. + </p> + <p> + Un dernier cas délicat reste : celui d'un objet créant + un autre objet, plus connu sous le patron de conception "fabrique" + (ou "factory"). + Supponsons qu'à la réussite d'une requête à + notre base de données imaginaire, un jeu d'enregistrements + est renvoyé sous la forme d'un itérateur, où chaque appel + au <span class="new_code">next()</span> sur notre itérateur donne une ligne + avant de s'arrêtre avec un false. + Cela semble à un cauchemar à simuler, alors qu'en fait un objet + fantaisie peut être créé avec les mécansimes ci-dessus... +<pre> +Mock::generate('DatabaseConnection'); +Mock::generate('ResultIterator'); + +class DatabaseTest extends UnitTestCase { + + function testUserFinderReadsResultsFromDatabase() {<strong> + $result = new MockResultIterator(); + $result->returns('next', false); + $result->returnsAt(0, 'next', array(1, 'tom')); + $result->returnsAt(1, 'next', array(3, 'dick')); + $result->returnsAt(2, 'next', array(6, 'harry')); + + $connection = new MockDatabaseConnection(); + $connection->returns('selectQuery', $result);</strong> + + $finder = new UserFinder(<strong>$connection</strong>); + $this->assertIdentical( + $finder->findNames(), + array('tom', 'dick', 'harry')); + } +} +</pre> + Désormais ce n'est que si notre <span class="new_code">$connection</span> + est appelée par la méthode <span class="new_code">query()</span> + que sera retourné le <span class="new_code">$result</span>, + lui-même s'arrêtant au troisième appel + à <span class="new_code">next()</span>. + Ce devrait être suffisant comme information + pour que notre classe <span class="new_code">UserFinder</span>, + la classe effectivement testée ici, + fasse son boulot. + Un test très précis et toujours pas + de base de données en vue. + </p> + <p> + Nous pourrsion affiner ce test encore plus + en insistant pour que la bonne requête + soit envoyée... +<pre> +$connection->returns('selectQuery', $result, array(<strong>'select name, id from people'</strong>)); +</pre> + Là, c'est une mauvaise idée. + </p> + <p> + Niveau objet, nous avons un <span class="new_code">UserFinder</span> + qui parle à une base de données à travers une interface géante - + l'ensemble du SQL. + Imaginez si nous avions écrit un grand nombre de tests + qui dépendrait désormais de cette requête SQL précise. + Ces requêtes pourraient changer en masse pour tout un tas + de raisons non liés à ce test spécifique. + Par exemple, la règle pour les quotes pourrait changer, + un nom de table pourrait évoluer, une table de liaison pourrait + être ajouté, etc. + Cela entrainerait une ré-écriture de tous les tests à chaque fois + qu'un remaniement est fait, alors même que le comportement lui + n'a pas bougé. + Les tests sont censés aider au remaniement, pas le bloquer. + Pour ma part, je préfère avoir une suite de tests qui passent + quand je fais évoluer le nom des tables. + </p> + <p> + Et si vous voulez une règle, c'est toujours mieux de ne pas + créer un objet fantaisie sur une grosse interface. + </p> + <p> + Par contrast, voici le test complet... +<pre> +class DatabaseTest extends UnitTestCase {<strong> + function setUp() { ... } + function tearDown() { ... }</strong> + + function testUserFinderReadsResultsFromDatabase() { + $finder = new UserFinder(<strong>new DatabaseConnection()</strong>); + $finder->add('tom'); + $finder->add('dick'); + $finder->add('harry'); + $this->assertIdentical( + $finder->findNames(), + array('tom', 'dick', 'harry')); + } +} +</pre> + Ce test est immunisé contre le changement de schéma. + Il échouera uniquement si vous changez la fonctionnalité, + ce qui correspond bien à ce qu'un test doit faire. + </p> + <p> + Il faut juste faire attention à ces méthodes <span class="new_code">setUp()</span> + et <span class="new_code">tearDown()</span> que nous avons survolé pour l'instant. + Elles doivent vider les tables de la base de données + et s'assurer que le schéma est bien défini. + Cela peut se engendrer un peu de travail supplémentaire, + mais d'ordinaire ce type de code existe déjà - à commencer pour + le déploiement. + </p> + <p> + Il y a au moins un endroit où vous aurez besoin d'objets fantaisie : + c'est la simulation des erreurs. + Exemple, la base de données tombe pendant que <span class="new_code">UserFinder</span> + fait son travail. Le <span class="new_code">UserFinder</span> se comporte-t-il bien ? +<pre> +class DatabaseTest extends UnitTestCase { + + function testUserFinder() { + $connection = new MockDatabaseConnection();<strong> + $connection->throwOn('selectQuery', new TimedOut('Ouch!'));</strong> + $alert = new MockAlerts();<strong> + $alert->expectOnce('notify', 'Database is busy - please retry');</strong> + $finder = new UserFinder($connection, $alert); + $this->assertIdentical($finder->findNames(), array()); + } +} +</pre> + Nous avons transmis au <span class="new_code">UserFinder</span> + un objet <span class="new_code">$alert</span>. + Il va transmettre un certain nombre de belles notifications + à l'interface utilisatuer en cas d'erreur. + Nous préfèrerions éviter de saupoudrer notre code avec + des commandes <span class="new_code">print</span> codées en dur si nous pouvons + l'éviter. + Emballer les moyens de sortie veut dire que nous pouvons utiliser + ce code partout. Et cela rend notre code plus facile. + </p> + <p> + Pour faire passer ce test, le finder doit écrire un message sympathique + et compréhensible à l'<span class="new_code">$alert</span>, plutôt que de propager + l'exception. Jusque là, tout va bien. + </p> + <p> + Comment faire en sorte que la <span class="new_code">DatabaseConnection</span> fantaisie + soulève une exception ? + Nous la générons avec la méthode <span class="new_code">throwOn</span> sur l'objet fantaisie. + </p> + <p> + Enfin, que se passe-t-il si la méthode voulue pour la simulation + n'existe pas encore ? + Si vous définissez une valeur de retour sur une méthode absente, + alors SimpleTest vous répondra avec une erreur. + Et si vous utilisez <span class="new_code">__call()</span> pour simuler + des méthodes dynamiques ? + </p> + <p> + Les objets avec des interfaces dynamiques, avec <span class="new_code">__call</span>, + peuvent être problématiques dans l'implémentation courante + des objets fantaisie. + Vous pouvez en créer un autour de la méthode <span class="new_code">__call()</span> + mais c'est très laid. + Et pourquoi un test devrait connaître quelque chose avec un niveau + si bas dans l'implémentation. Il n'a besoin que de simuler l'appel. + </p> + <p> + Il y a bien moyen de contournement : ajouter des méthodes complémentaires + à l'objet fantaisie à la génération. +<pre> +<strong>Mock::generate('DatabaseConnection', 'MockDatabaseConnection', array('setOptions'));</strong> +</pre> + Dans une longue suite de tests cela pourrait entraîner des problèmes, + puisque vous avez probablement déjà une version fantaisie + de la classe appellée <span class="new_code">MockDatabaseConnection</span> + sans les méthodes complémentaires. + Le générateur de code ne générera pas la version fantaisie de la classe + s'il en existe déjà une version avec le même nom. + Il vous deviendra impossible de déterminer où est passée votre méthode + si une autre définition a été lancé au préalable. + </p> + <p> + Pour pallier à ce problème, SimpleTest vous permet de choisir + n'importe autre nom pour la nouvelle classe au moment même où + vous ajouter les méthodes complémentaires. +<pre> +Mock::generate('DatabaseConnection', <strong>'MockDatabaseConnectionWithOptions'</strong>, array('setOptions')); +</pre> + Ici l'objet fantaisie se comportera comme si + <span class="new_code">setOptions()</span> existait bel et bien + dans la classe originale. + </p> + <p> + Les objets fantaisie ne peuvent être utilisés qu'à l'intérieur + des scénarios de test, étant donné qu'à l'apparition d'une attente + ils envoient des messages directement au scénario de test courant. + Les créer en dehors d'un scénario de test entraînera une erreur + de run time quand une attente est déclenchée et qu'il n'y a pas + de scénario de test en cours pour recevoir le message. + Nous pouvons désormais couvrir ces attentes. + </p> + + <h2> +<a class="target" name="expectations"></a>Objets fantaisie en tant que critiques</h2> + <p> + Même si les bouchons serveur isolent vos tests des perturbations + du monde réel, ils n'apportent que le moitié des bénéfices possibles. + Vous pouvez obtenir une classe de test qui reçoive les bons messages, + mais cette nouvelle classe envoie-t-elle les bons ? + Le tester peut devenir très bordélique sans + une librairie d'objets fantaise. + </p> + <p> + Voici un exemple, prenons une classe <span class="new_code">PageController</span> + toute simple qui traitera un formulaire de paiement + par carte bleue... +<pre> +class PaymentForm extends PageController { + function __construct($alert, $payment_gateway) { ... } + function makePayment($request) { ... } +} +</pre> + Cette classe a besoin d'un <span class="new_code">PaymentGateway</span> + pour parler concrètement à la banque. + Il utilise aussi un objet <span class="new_code">Alert</span> + pour gérer les erreurs. + Cette dernière classe parle à la page ou au template. + Elle est responsable de l'affichage du message d'erreur + et de la mise en valeur de tout champ du formulaire + qui serait incorrecte. + </p> + <p> + Elle pourrait ressembler à... +<pre> +class Alert { + function warn($warning, $id) { + print '<div class="warning">' . $warning . '</div>'; + print '<style type="text/css">#' . $id . ' { background-color: red }</style>'; + } +} +</pre> + </p> + <p> + Portons notre attention à la méthode <span class="new_code">makePayment()</span>. + Si nous n'inscrivons pas un numéro "CVV2" (celui à trois + chiffre au dos de la carte bleue), nous souhaitons afficher + une erreur plutôt que d'essayer de traiter le paiement. + En mode test... +<pre> +<?php +require_once('simpletest/autorun.php'); +require_once('payment_form.php'); +Mock::generate('Alert'); +Mock::generate('PaymentGateway'); + +class PaymentFormFailuresShouldBeGraceful extends UnitTestCase { + + function testMissingCvv2CausesAlert() { + $alert = new MockAlert(); + <strong>$alert->expectOnce( + 'warn', + array('Missing three digit security code', 'cvv2'));</strong> + $controller = new PaymentForm(<strong>$alert</strong>, new MockPaymentGateway()); + $controller->makePayment($this->requestWithMissingCvv2()); + } + + function requestWithMissingCvv2() { ... } +} +?> +</pre> + Première question : où sont passés les assertions ? + </p> + <p> + L'appel à <span class="new_code">expectOnce('warn', array(...))</span> annonce + à l'objet fantaisie qu'il faut s'attendre à un appel à <span class="new_code">warn()</span> + avant la fin du test. + Quand il débouche sur l'appel à <span class="new_code">warn()</span>, il vérifie + les arguments. Si ceux-ci ne correspondent pas, alors un échec + est généré. Il échouera aussi si la méthode n'est jamais appelée. + </p> + <p> + Non seulement le test ci-dessus s'assure que <span class="new_code">warn</span> + a bien été appelé, mais en plus qu'il a bien reçu la chaîne + de caractère "Missing three digit security code" + et même le tag "cvv2". + L'équivalent de <span class="new_code">assertIdentical()</span> est appliqué + aux deux champs quand les paramètres sont comparés. + </p> + <p> + Si le contenu du message vous importe peu, surtout dans le cas + d'une interface utilisateur qui change régulièrement, + nous pouvons passer ce paramètre avec l'opérateur "*"... +<pre> +class PaymentFormFailuresShouldBeGraceful extends UnitTestCase { + + function testMissingCvv2CausesAlert() { + $alert = new MockAlert(); + $alert->expectOnce('warn', array(<strong>'*'</strong>, 'cvv2')); + $controller = new PaymentForm($alert, new MockPaymentGateway()); + $controller->makePayment($this->requestWithMissingCvv2()); + } + + function requestWithMissingCvv2() { ... } +} +</pre> + Nous pouvons même rendre le test encore moins spécifique + en supprimant complètement la liste des paramètres... +<pre> +function testMissingCvv2CausesAlert() { + $alert = new MockAlert(); + <strong>$alert->expectOnce('warn');</strong> + $controller = new PaymentForm($alert, new MockPaymentGateway()); + $controller->makePayment($this->requestWithMissingCvv2()); +} +</pre> + Ceci vérifiera uniquement si la méthode a été appelé, + ce qui est peut-être un peu drastique dans ce cas. + Plus tard, nous verrons comment alléger les attentes + plus précisement. + </p> + <p> + Des tests sans assertions peuvent être à la fois compacts + et très expressifs. Parce que nous interceptons l'appel + sur le chemin de l'objet, ici de classe <span class="new_code">Alert</span>, + nous évitons de tester l'état par la suite. + Cela évite les assertions dans les tests, mais aussi + l'obligation d'ajouter des accesseurs uniquement + pour les tests dans le code original. + Si vous en arrivez à ajouter des accesseurs de ce type, + on parle alors de "state based testing" dans le jargon + ("test piloté par l'état"), + il est probablement plus que temps d'utiliser + des objets fantaisie dans vos tests. + On peut alors parler de "behaviour based testing" + (ou "test piloté par le comportement") : + c'est largement mieux ! + </p> + <p> + Ajoutons un autre test. + Assurons nous que nous essayons même pas un paiement sans CVV2... +<pre> +class PaymentFormFailuresShouldBeGraceful extends UnitTestCase { + + function testMissingCvv2CausesAlert() { ... } + + function testNoPaymentAttemptedWithMissingCvv2() { + $payment_gateway = new MockPaymentGateway(); + <strong>$payment_gateway->expectNever('pay');</strong> + $controller = new PaymentForm(new MockAlert(), $payment_gateway); + $controller->makePayment($this->requestWithMissingCvv2()); + } + + ... +} +</pre> + Vérifier une négation peut être très difficile + dans les tests, mais <span class="new_code">expectNever()</span> + rend l'opération très facile heureusement. + </p> + <p> + <span class="new_code">expectOnce()</span> et <span class="new_code">expectNever()</span> sont + suffisants pour la plupart des tests, mais + occasionnellement vous voulez tester plusieurs évènements. + D'ordinaire pour des raisons d'usabilité, nous souhaitons + que tous les champs manquants du formulaire soient + mis en relief, et pas uniquement le premier. + Cela veut dire que nous devrions voir de multiples appels + à <span class="new_code">Alert::warn()</span>, pas juste un... +<pre> +function testAllRequiredFieldsHighlightedOnEmptyRequest() { + $alert = new MockAlert();<strong> + $alert->expectAt(0, 'warn', array('*', 'cc_number')); + $alert->expectAt(1, 'warn', array('*', 'expiry')); + $alert->expectAt(2, 'warn', array('*', 'cvv2')); + $alert->expectAt(3, 'warn', array('*', 'card_holder')); + $alert->expectAt(4, 'warn', array('*', 'address')); + $alert->expectAt(5, 'warn', array('*', 'postcode')); + $alert->expectAt(6, 'warn', array('*', 'country')); + $alert->expectCallCount('warn', 7);</strong> + $controller = new PaymentForm($alert, new MockPaymentGateway()); + $controller->makePayment($this->requestWithMissingCvv2()); +} +</pre> + Le compteur dans <span class="new_code">expectAt()</span> précise + le nombre de fois que la méthode a déjà été appelée. + Ici nous vérifions que chaque champ sera bien mis en relief. + </p> + <p> + Notez que nous sommes forcé de tester l'ordre en même temps. + SimpleTest n'a pas encore de moyen pour éviter cela, + mais dans une version future ce sera corrigé. + </p> + <p> + Voici la liste complètes des attentes + que vous pouvez préciser sur une objet fantaisie + dans <a href="http://simpletest.org/">SimpleTest</a>. + Comme pour les assertions, ces méthodes prennent en option + un message d'erreur. + <table> + <thead><tr> +<th>Attente</th> +<th>Description</th> +</tr></thead> + <tbody> + <tr> + <td><span class="new_code">expect($method, $args)</span></td> + <td>Les arguements doivent correspondre si appelés</td> + </tr> + <tr> + <td><span class="new_code">expectAt($timing, $method, $args)</span></td> + <td>Les arguements doiven correspondre si appelés lors du passage numéro <span class="new_code">$timing</span> +</td> + </tr> + <tr> + <td><span class="new_code">expectCallCount($method, $count)</span></td> + <td>La méthode doit être appelée exactement <span class="new_code">$count</span> fois</td> + </tr> + <tr> + <td><span class="new_code">expectMaximumCallCount($method, $count)</span></td> + <td>La méthode ne doit pas être appelée plus de <span class="new_code">$count</span> fois</td> + </tr> + <tr> + <td><span class="new_code">expectMinimumCallCount($method, $count)</span></td> + <td>La méthode ne doit pas être appelée moins de <span class="new_code">$count</span> fois</td> + </tr> + <tr> + <td><span class="new_code">expectNever($method)</span></td> + <td>La méthode ne doit jamais être appelée</td> + </tr> + <tr> + <td><span class="new_code">expectOnce($method, $args)</span></td> + <td>La méthode ne doit être appelée qu'une seule fois et avec les arguments (en option)</td> + </tr> + <tr> + <td><span class="new_code">expectAtLeastOnce($method, $args)</span></td> + <td>La méthode doit être appelée au moins une seule fois et toujours avec au moins un des arguments attendus</td> + </tr> + </tbody> + </table> + Où les paramètres sont... + <dl> + <dt class="new_code">$method</dt> + <dd> + Le nom de la méthode, sous la forme d'une chaîne de caractères, + à laquelle il faut appliquer la condition. + </dd> + <dt class="new_code">$args</dt> + <dd> + Les argumetns sous la forme d'une liste. + Les jokers peuvent être inclus de la même manière + que pour <span class="new_code">setReturn()</span>. + Cet argument est optionnel pour <span class="new_code">expectOnce()</span> + et <span class="new_code">expectAtLeastOnce()</span>. + </dd> + <dt class="new_code">$timing</dt> + <dd> + La seule marque dans le temps pour tester la condition. + Le premier appel commence à zéro et le comptage se fait + séparement sur chaque méthode. + </dd> + <dt class="new_code">$count</dt> + <dd>Le nombre d'appels attendu.</dd> + </dl> + </p> + <p> + Si vous n'avez qu'un seul appel dans votre test, assurez vous + d'utiliser <span class="new_code">expectOnce</span>.<br> + Utiliser <span class="new_code">$mocked->expectAt(0, 'method', 'args);</span> + tout seul ne permettra qu'à la méthode de ne jamais être appelée. + Vérifier les arguements et le comptage total sont pour le moment + indépendants. + Ajouter une attente <span class="new_code">expectCallCount()</span> quand + vous utilisez <span class="new_code">expectAt()</span> (dans le cas sans appel) + est permis. + </p> + <p> + Comme les assertions à l'intérieur des scénarios de test, + toutes ces attentes peuvent incorporer une surchage + sur le message sous la forme d'un paramètre supplémentaire. + Par ailleurs le message original peut être inclus dans la sortie + avec "%s". + </p> + + </div> + References and related information... + <ul> +<li> + Le papier original sur les Objets fantaisie ou + <a href="http://www.mockobjects.com/">Mock objects</a>. + </li> +<li> + La page du projet SimpleTest sur <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>. + </li> +<li> + La page d'accueil de SimpleTest sur <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>. + </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> |