779 lines
35 KiB
HTML
779 lines
35 KiB
HTML
|
<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="#quoi">Que sont les objets fantaisie ?</a>
|
||
|
</li>
|
||
|
<li>
|
||
|
<a href="#creation">Créer des objets fantaisie</a>.
|
||
|
</li>
|
||
|
<li>
|
||
|
<a href="#bouchon">L'objet fantaisie - acteur</a> ou bouchon.
|
||
|
</li>
|
||
|
<li>
|
||
|
<a href="#attentes">L'objet fantaisie - critique</a> avec des attentes.
|
||
|
</li>
|
||
|
<li>
|
||
|
<a href="#approches">D'autres approches</a>
|
||
|
y compris des librairies d'objets fantaisie.
|
||
|
</li>
|
||
|
<li>
|
||
|
Utiliser les objets fantaisie avec
|
||
|
<a href="#autres_testeurs">d'autres testeurs unitaires</a>.
|
||
|
</li>
|
||
|
</ul>
|
||
|
<div class="content">
|
||
|
<p><a class="target" name="quoi"><h2>Que sont les objets fantaisie ?</h2></a></p>
|
||
|
<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>.
|
||
|
</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.
|
||
|
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>
|
||
|
|
||
|
<p><a class="target" name="creation"><h2>Créer des objets fantaisie</h2></a></p>
|
||
|
<p>
|
||
|
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 à...
|
||
|
<pre>
|
||
|
<strong>class DatabaseConnection {
|
||
|
function DatabaseConnection() {
|
||
|
}
|
||
|
|
||
|
function query() {
|
||
|
}
|
||
|
|
||
|
function selectQuery() {
|
||
|
}
|
||
|
}</strong>
|
||
|
</pre>
|
||
|
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...
|
||
|
<pre>
|
||
|
<strong>require_once('simpletest/unit_tester.php');
|
||
|
require_once('simpletest/mock_objects.php');
|
||
|
require_once('database_connection.php');
|
||
|
|
||
|
Mock::generate('DatabaseConnection');</strong>
|
||
|
</pre>
|
||
|
Ceci génère une classe clone appelée <span class="new_code">MockDatabaseConnection</span>.
|
||
|
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/unit_tester.php');
|
||
|
require_once('simpletest/mock_objects.php');
|
||
|
require_once('database_connection.php');
|
||
|
|
||
|
Mock::generate('DatabaseConnection');
|
||
|
<strong>
|
||
|
class MyTestCase extends UnitTestCase {
|
||
|
|
||
|
function testSomething() {
|
||
|
$connection = &new MockDatabaseConnection($this);
|
||
|
}
|
||
|
}</strong>
|
||
|
</pre>
|
||
|
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.
|
||
|
</p>
|
||
|
|
||
|
<p><a class="target" name="bouchon"><h2>Objets fantaisie en action</h2></a></p>
|
||
|
<p>
|
||
|
La version fantaisie d'une classe contient
|
||
|
toutes les méthodes de l'originale.
|
||
|
De la sorte une opération comme
|
||
|
<span class="new_code">$connection->query()</span>
|
||
|
est encore possible.
|
||
|
Tout comme avec les bouchons, nous pouvons remplacer
|
||
|
la valeur nulle renvoyée par défaut...
|
||
|
<pre>
|
||
|
<strong>$connection->setReturnValue('query', 37);</strong>
|
||
|
</pre>
|
||
|
Désormais à chaque appel de
|
||
|
<span class="new_code">$connection->query()</span>
|
||
|
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...
|
||
|
<pre>
|
||
|
<strong>Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions'));</strong>
|
||
|
</pre>
|
||
|
Ici l'objet fantaisie se comportera comme
|
||
|
si <span class="new_code">setOptions()</span> existait dans la classe originale.
|
||
|
C'est pratique si une classe a utilisé le mécanisme
|
||
|
<span class="new_code">overload()</span> de PHP pour ajouter des méthodes dynamiques.
|
||
|
Vous pouvez créer des fantaisies spéciales pour simuler cette situation.
|
||
|
</p>
|
||
|
<p>
|
||
|
Tous les modèles disponibles avec les bouchons serveur
|
||
|
le sont également avec les objets fantaisie...
|
||
|
<pre>
|
||
|
class Iterator {
|
||
|
function Iterator() {
|
||
|
}
|
||
|
|
||
|
function next() {
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
Une nouvelle fois, supposons que cet itérateur
|
||
|
ne retourne que du texte jusqu'au moment où il atteint
|
||
|
son terme, quand il renvoie <span class="new_code">false</span>.
|
||
|
Nous pouvons le simuler avec...
|
||
|
<pre>
|
||
|
Mock::generate('Iterator');
|
||
|
|
||
|
class IteratorTest extends UnitTestCase() {
|
||
|
|
||
|
function testASequence() {<strong>
|
||
|
$iterator = &new MockIterator($this);
|
||
|
$iterator->setReturnValue('next', false);
|
||
|
$iterator->setReturnValueAt(0, 'next', 'First string');
|
||
|
$iterator->setReturnValueAt(1, 'next', 'Second string');</strong>
|
||
|
...
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
Au moment du premier appel à <span class="new_code">next()</span>
|
||
|
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 <span class="new_code">false</span>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
Reprenons aussi le conteneur d'information bouchonné
|
||
|
avec des pairs clef / valeur...
|
||
|
<pre>
|
||
|
class Configuration {
|
||
|
function Configuration() {
|
||
|
}
|
||
|
|
||
|
function getValue($key) {
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
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 <span class="new_code">getValue()</span>
|
||
|
et que nous voulons des résultats différents pour des clefs différentes.
|
||
|
Heureusement les objets fantaisie ont un système de filtrage...
|
||
|
<pre>
|
||
|
<strong>$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'));</strong>
|
||
|
</pre>
|
||
|
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 <span class="new_code">getValue()</span>
|
||
|
est invoquée sur l'objet fantaisie...
|
||
|
<pre>
|
||
|
$config->getValue('db_user')
|
||
|
</pre>
|
||
|
...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.
|
||
|
</p>
|
||
|
<p>
|
||
|
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...
|
||
|
<pre>
|
||
|
class Thing {
|
||
|
}
|
||
|
|
||
|
class Vector {
|
||
|
function Vector() {
|
||
|
}
|
||
|
|
||
|
function get($index) {
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
Dans ce cas vous pouvez placer une référence
|
||
|
dans la liste renvoyée par l'objet fantaisie...
|
||
|
<pre>
|
||
|
$thing = new Thing();<strong>
|
||
|
$vector = &new MockVector($this);
|
||
|
$vector->setReturnReference('get', $thing, array(12));</strong>
|
||
|
</pre>
|
||
|
Avec cet arrangement vous savez qu'à chaque appel
|
||
|
de <span class="new_code">$vector->get(12)</span>
|
||
|
le même <span class="new_code">$thing</span> sera renvoyé.
|
||
|
</p>
|
||
|
|
||
|
<p><a class="target" name="attentes"><h2>Objets fantaisie en critique</h2></a></p>
|
||
|
<p>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
Pour l'exemple, prenons une classe <span class="new_code">SessionPool</span>
|
||
|
à 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 <span class="new_code">SessionPool</span> ressemble à...
|
||
|
<pre>
|
||
|
<strong>class SessionPool {
|
||
|
function SessionPool() {
|
||
|
...
|
||
|
}
|
||
|
|
||
|
function &findSession($cookie) {
|
||
|
...
|
||
|
}
|
||
|
...
|
||
|
}
|
||
|
|
||
|
class Session {
|
||
|
...
|
||
|
}</strong>
|
||
|
|
||
|
</pre>
|
||
|
Alors que pour notre code de log, nous avons...
|
||
|
<pre><strong>
|
||
|
class Log {
|
||
|
function Log() {
|
||
|
...
|
||
|
}
|
||
|
|
||
|
function message() {
|
||
|
...
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class LoggingSessionPool {
|
||
|
function LoggingSessionPool(&$session_pool, &$log) {
|
||
|
...
|
||
|
}
|
||
|
|
||
|
function &findSession($cookie) {
|
||
|
...
|
||
|
}
|
||
|
...
|
||
|
}</strong>
|
||
|
</pre>
|
||
|
Dans tout ceci, la seule classe à tester est
|
||
|
<span class="new_code">LoggingSessionPool</span>. En particulier,
|
||
|
nous voulons vérifier que la méthode <span class="new_code">findSession()</span>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
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 :
|
||
|
<ol>
|
||
|
<li>Créer un objet de log.</li>
|
||
|
<li>Indiquer le répertoire d'écriture du fichier de log.</li>
|
||
|
<li>Modifier les droits sur le répertoire pour pouvoir y écrire le fichier.</li>
|
||
|
<li>Créer un objet <span class="new_code">SessionPool</span>.</li>
|
||
|
<li>Lancer une session, ce qui demande probablement pas mal de choses.</li>
|
||
|
<li>Invoquer <span class="new_code">findSession()</span>.</li>
|
||
|
<li>Lire le nouvel identifiant de session (en espérant qu'il existe un accesseur !).</li>
|
||
|
<li>Lever une assertion de test pour vérifier que cet identifiant correspond bien au cookie.</li>
|
||
|
<li>Lire la dernière ligne du fichier de log.</li>
|
||
|
<li>Supprimer avec une (ou plusieurs) expression rationnelle les timestamps de log en trop, etc.</li>
|
||
|
<li>Vérifier que le message de session est bien dans le texte.</li>
|
||
|
</ol>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
A la place, voici la méthode complète pour le test
|
||
|
avec un peu de magie via les objets fantaisie...
|
||
|
<pre>
|
||
|
Mock::generate('Session');
|
||
|
Mock::generate('SessionPool');
|
||
|
Mock::generate('Log');
|
||
|
|
||
|
class LoggingSessionPoolTest extends UnitTestCase {
|
||
|
...
|
||
|
function testFindSessionLogging() {<strong>
|
||
|
$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();</strong>
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
<span class="new_code">findSession()</span> est un méthode fabrique
|
||
|
dont la simulation est décrite <a href="#stub">plus haut</a>.
|
||
|
Le point de départ vient avec le premier appel
|
||
|
<span class="new_code">expectOnce()</span>. Cette ligne indique
|
||
|
qu'à chaque fois que <span class="new_code">findSession()</span>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
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 <span class="new_code">tally()</span> à la fin du test.
|
||
|
</p>
|
||
|
<p>
|
||
|
Nous utilisons le même modèle pour mettre sur pied
|
||
|
le loggueur fantaisie. Nous lui indiquons que <span class="new_code">message()</span>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
Nous commençons le lancement nos tests à la création
|
||
|
du nouveau <span class="new_code">LoggingSessionPool</span>
|
||
|
et nous l'alimentons avec nos objets fantaisie juste créés.
|
||
|
Désormais tout est sous contrôle. Au final nous confirmons
|
||
|
que le <span class="new_code">$session</span> 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 <span class="new_code">tally()</span>.
|
||
|
</p>
|
||
|
<p>
|
||
|
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 <span class="new_code">setUp()</span>.
|
||
|
</p>
|
||
|
<p>
|
||
|
Voici la liste complète des attentes que vous pouvez
|
||
|
placer sur un objet fantaisie avec
|
||
|
<a href="http://www.lastcraft.com/simple_test.php">SimpleTest</a>...
|
||
|
<table>
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th>Attente</th>
|
||
|
<th>Nécessite <span class="new_code">tally()</span>
|
||
|
</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
<tr>
|
||
|
<td><span class="new_code">expectArguments($method, $args)</span></td>
|
||
|
<td style="text-align: center">Non</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td><span class="new_code">expectArgumentsAt($timing, $method, $args)</span></td>
|
||
|
<td style="text-align: center">Non</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td><span class="new_code">expectCallCount($method, $count)</span></td>
|
||
|
<td style="text-align: center">Oui</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td><span class="new_code">expectMaximumCallCount($method, $count)</span></td>
|
||
|
<td style="text-align: center">Non</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td><span class="new_code">expectMinimumCallCount($method, $count)</span></td>
|
||
|
<td style="text-align: center">Oui</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td><span class="new_code">expectNever($method)</span></td>
|
||
|
<td style="text-align: center">Non</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td><span class="new_code">expectOnce($method, $args)</span></td>
|
||
|
<td style="text-align: center">Oui</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td><span class="new_code">expectAtLeastOnce($method, $args)</span></td>
|
||
|
<td style="text-align: center">Oui</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,
|
||
|
à laquelle la condition doit être appliquée.</dd>
|
||
|
<dt class="new_code">$args</dt>
|
||
|
<dd>
|
||
|
Les arguments sous la forme d'une liste.
|
||
|
Les jokers peuvent être inclus de la même manière
|
||
|
qu'avec <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>
|
||
|
Le seul point dans le temps pour tester
|
||
|
la condition. Le premier appel commence à zéro.
|
||
|
</dd>
|
||
|
<dt class="new_code">$count</dt>
|
||
|
<dd>Le nombre d'appels attendu.</dd>
|
||
|
</dl>
|
||
|
La méthode <span class="new_code">expectMaximumCallCount()</span>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
Par ailleurs si vous avez just un appel dans votre test,
|
||
|
vérifiez bien que vous utiliser
|
||
|
<span class="new_code">expectOnce</span>.<br>
|
||
|
Utiliser <span class="new_code">$mocked->expectAt(0, 'method', 'args);</span>
|
||
|
tout seul ne sera pas pris en compte :
|
||
|
la vérification des arguments et le comptage total
|
||
|
sont pour l'instant encore indépendant.
|
||
|
</p>
|
||
|
<p>
|
||
|
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".
|
||
|
</p>
|
||
|
|
||
|
<p><a class="target" name="approches"><h2>D'autres approches</h2></a></p>
|
||
|
<p>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
Les objets fantaisie générés avec
|
||
|
<a href="simple_test.html">SimpleTest</a> sont dynamiques.
|
||
|
Ils sont créés à l'exécution dans la mémoire,
|
||
|
grâce à <span class="new_code">eval()</span>, 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.
|
||
|
</p>
|
||
|
<p>
|
||
|
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.
|
||
|
</p>
|
||
|
<p>
|
||
|
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
|
||
|
<em>mocks/connection.php</em> 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...
|
||
|
<pre>
|
||
|
<?php
|
||
|
require_once('simpletest/mock_objects.php');
|
||
|
require_once('../classes/connection.php');
|
||
|
<strong>
|
||
|
Mock::generate('Connection', 'BasicMockConnection');
|
||
|
class MockConnection extends BasicMockConnection {
|
||
|
function MockConnection(&$test, $wildcard = '*') {
|
||
|
$this->BasicMockConnection($test, $wildcard);
|
||
|
$this->setReturn('query', false);
|
||
|
}
|
||
|
}</strong>
|
||
|
?>
|
||
|
</pre>
|
||
|
L'appel <span class="new_code">generate</span> dit au générateur de classe
|
||
|
d'en créer une appelée <span class="new_code">BasicMockConnection</span>
|
||
|
plutôt que la plus courante <span class="new_code">MockConnection</span>.
|
||
|
Ensuite nous héritons à partir de celle-ci pour obtenir
|
||
|
notre version de <span class="new_code">MockConnection</span>.
|
||
|
En interceptant de cette manière nous pouvons ajouter
|
||
|
un comportement, ici transformer la valeur par défaut de
|
||
|
<span class="new_code">query()</span> 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 <span class="new_code">MockConnection</span> 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.
|
||
|
</p>
|
||
|
<p>
|
||
|
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.
|
||
|
</p>
|
||
|
|
||
|
<p><a class="target" name="autres_testeurs"><h2>Je pense que SimpleTest pue !</h2></a></p>
|
||
|
<p>
|
||
|
Mais au moment d'écrire ces lignes c'est le seul
|
||
|
à gérer les objets fantaisie, donc vous êtes bloqué avec lui ?
|
||
|
</p>
|
||
|
<p>
|
||
|
Non, pas du tout.
|
||
|
<a href="simple_test.html">SimpleTest</a> 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 à...
|
||
|
<pre>
|
||
|
class PHPUnit {
|
||
|
function PHPUnit() {
|
||
|
}
|
||
|
|
||
|
function assertion($message, $assertion) {
|
||
|
}
|
||
|
...
|
||
|
}
|
||
|
</pre>
|
||
|
La seule chose que la méthode <span class="new_code">assertion()</span> 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...
|
||
|
<pre>
|
||
|
$unit_test = new PHPUnit();
|
||
|
$unit_test>assertion('I hope this file exists', file_exists('my_file'));
|
||
|
</pre>
|
||
|
Comment utiliser les objets fantaisie avec ceci ?
|
||
|
</p>
|
||
|
<p>
|
||
|
Il y a une méthode protégée sur la classe de base
|
||
|
des objets fantaisie : elle s'appelle <span class="new_code">_assertTrue()</span>.
|
||
|
En surchargeant cette méthode nous pouvons utiliser
|
||
|
notre propre format d'assertion.
|
||
|
Nous commençons avec une sous-classe, dans <em>my_mock.php</em>...
|
||
|
<pre>
|
||
|
<strong><?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);
|
||
|
}
|
||
|
}
|
||
|
?></strong>
|
||
|
</pre>
|
||
|
Maintenant une instance de <span class="new_code">MyMock</span>
|
||
|
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...
|
||
|
<pre>
|
||
|
<?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);
|
||
|
}
|
||
|
}<strong>
|
||
|
SimpleTestOptions::setMockBaseClass('MyMock');</strong>
|
||
|
?>
|
||
|
</pre>
|
||
|
A partir de maintenant vous avez juste à inclure
|
||
|
<em>my_mock.php</em> à la place de la version par défaut
|
||
|
<em>simple_mock.php</em> et vous pouvez introduire
|
||
|
des objets fantaisie dans votre suite de tests existants.
|
||
|
</p>
|
||
|
|
||
|
</div>
|
||
|
References and related information...
|
||
|
<ul>
|
||
|
<li>
|
||
|
L'article originel sur
|
||
|
<a href="http://www.mockobjects.com/">les objets fantaisie</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>
|