<?xml version="1.0" encoding="ISO-8859-1" ?>
<!-- $Id: simple_test.xml 1701 2008-03-24 20:08:06Z pp11 $ -->
<page title="Prise en main rapide de SimpleTest" here="Prise en main rapide de SimpleTest">
    <synchronisation lang="en" version="1687" date="24/03/2008" maintainer="pp11" />
    <long_title>
        Prise en main rapide de SimpleTest pour PHP -
        Tests unitaire et objets fantaisie pour PHP
    </long_title>
    <content>
        <introduction>
            <p>
                Le présent article présuppose que vous soyez familier avec
                le concept de tests unitaires ainsi que celui de développement
                web avec le langage PHP. Il s'agit d'un guide pour le nouvel
                et impatient utilisateur de
                <a href="https://sourceforge.net/project/showfiles.php?group_id=76550">SimpleTest</a>.
                Pour une documentation plus complète, particulièrement si
                vous découvrez les tests unitaires, consultez la
                <a href="http://www.lastcraft.com/unit_test_documentation.php">documentation
                en cours</a>, et pour des exemples de scénarios de test,
                consultez le
                <a href="http://www.lastcraft.com/first_test_tutorial.php">tutorial
                sur les tests unitaires</a>.
            </p>
        </introduction>
        <section name="unit" title="Utiliser le testeur rapidement">
            <p>
                Parmi les outils de test pour logiciel, le testeur unitaire
                est le plus proche du développeur. Dans un contexte de
                développement agile, le code de test se place juste à côté
                du code source étant donné que tous les deux sont écrits
                simultanément. Dans ce contexte, SimpleTest aspire à être
                une solution complète de test pour un développeur PHP et
                s'appelle "Simple" parce qu'elle devrait être simple à
                utiliser et à étendre. Ce nom n'était pas vraiment un bon
                choix. Non seulement cette solution inclut toutes les
                fonctions classiques qu'on est en droit d'attendre de la
                part des portages de <a
                href="http://www.junit.org/">JUnit</a> et des <a
                href="http://sourceforge.net/projects/phpunit/">PHPUnit</a>,
                mais elle inclut aussi les <a
                href="http://www.mockobjects.com/">objets fantaisie ou
                "mock objects"</a>.
            </p>
            <p>
                Ce qui rend cet outil immédiatement utile pour un développeur PHP,
                c'est son navigateur web interne.
                Il permet des tests qui parcourent des sites web, remplissent
                des formulaires et testent le contenu des pages.
                Etre capable d'écrire ces tests en PHP veut dire qu'il devient
                facile d'écrire des tests de recette (ou d'intégration).
                Un exemple serait de confirmer qu'un utilisateur a bien été ajouté
                dans une base de données après s'être enregistré sur une site web.
            </p>
            <p>
                La démonstration la plus rapide : l'exemple
            </p>
            <p>
                Supposons que nous sommes en train de tester une simple
                classe de log dans un fichier : elle s'appelle
                <code>Log</code> dans <em>classes/Log.php</em>. Commençons
                par créer un script de test, appelé
                <em>tests/log_test.php</em>. Son contenu est le suivant...
<php><![CDATA[
<?php
<strong>require_once('simpletest/autorun.php');</strong>
require_once('../classes/log.php');

class TestOfLogging extends <strong>UnitTestCase</strong> {
}
?>
]]></php>
                Ici le répertoire <em>simpletest</em> est soit dans le
                dossier courant, soit dans les dossiers pour fichiers
                inclus. Vous auriez à éditer ces arborescences suivant
                l'endroit où vous avez installé SimpleTest.
                Le fichier &quot;autorun.php&quot; fait plus que juste inclure
                les éléments de SimpleTest : il lance aussi les tests pour nous.  
            </p>
            <p>
                <code>TestOfLogging</code> est notre premier scénario de test
                et il est pour l'instant vide.
                Chaque scénario de test est une classe qui étend une des classes
                de base de SimpleTest. Nous pouvons avoir autant de classes de ce type
                que nous voulons.
            </p>
            <p>
                Avec ces trois lignes d'échafaudage
                l'inclusion de notre classe <code>Log</code>, nous avons une suite
                de tests. Mais pas encore de test !
            </p>
            <p>
                Pour notre premier test, supposons que la classe <code>Log</code>
                prenne le nom du fichier à écrire au sein du constructeur,
                et que nous avons un répertoire temporaire dans lequel placer
                ce fichier.
<php><![CDATA[
<?php
require_once('simpletest/autorun.php');
require_once('../classes/log.php');

class TestOfLogging extends UnitTestCase {
    function <strong>testLogCreatesNewFileOnFirstMessage()</strong> {
        @unlink('/temp/test.log');
        $log = new Log('/temp/test.log');
        <strong>$this->assertFalse(file_exists('/temp/test.log'));</strong>
        $log->message('Should write this to a file');
        <strong>$this->assertTrue(file_exists('/temp/test.log'));</strong>
    }
}
?>
]]></php>
                Au lancement du scénario de test, toutes les méthodes qui
                commencent avec la chaîne <code>test</code> sont
                identifiées puis exécutées.
                Si la méthode commence par <code>test</code>, c'est un test.
                Remarquez bien le nom très long de notre exemple :
                <code>testLogCreatesNewFileOnFirstMessage()</code>.
                C'est bel et bien délibéré : ce style est considéré désirable
                et il rend la sortie du test plus lisible.
            </p>
            <p>
                D'ordinaire nous avons bien plusieurs méthodes de tests.
                Mais ce sera pour plus tard.
            </p>
            <p>
                Les assertions dans les
                méthodes de test envoient des messages vers le framework de
                test qui affiche immédiatement le résultat. Cette réponse
                immédiate est importante, non seulement lors d'un crash
                causé par le code, mais aussi de manière à rapprocher
                l'affichage de l'erreur au plus près du scénario de test
                concerné via un appel à <code>print</code>code>.
            </p>
            <p>
                Pour voir ces résultats lançons effectivement les tests.
                Aucun autre code n'est nécessaire, il suffit d'ouvrir
                la page dans un navigateur.
            </p>
            <p>
                En cas échec, l'affichage ressemble à...
                <div class="demo">
                    <h1>TestOfLogging</h1>
                    <span class="fail">Fail</span>: testcreatingnewfile-&gt;True assertion failed.<br />
                    <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
                    <strong>1</strong> passes and <strong>1</strong> fails.</div>
                </div>
                ...et si ça passe, on obtient...
                <div class="demo">
                    <h1>TestOfLogging</h1>
                    <div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
                    <strong>2</strong> passes and <strong>0</strong> fails.</div>
                </div>
                Et si vous obtenez ça...
                <div class="demo">
                    <b>Fatal error</b>:  Failed opening required '../classes/log.php' (include_path='') in <b>/home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php</b> on line <b>7</b>
                </div>
                c'est qu'il vous manque le fichier <em>classes/Log.php</em>
                qui pourrait ressembler à : 
<php><![CDATA[
<?php<strong>
class Log {
    function Log($file_path) {
    }

    function message() {
    }
}</strong>
?>
]]></php>
                C'est largement plus sympathique d'écrire le code après le test.
                Plus que sympatique même - cette technique s'appelle
                &quot;Développement Piloté par les Tests&quot; ou 
                &quot;Test Driven Development&quot; en anglais.
            </p>
            <p>
                Pour plus de renseignements sur le testeur, voyez la
                <a local="unit_test_documentation">documentation pour les tests de régression</a>.
            </p>
        </section>
        <section name="group" title="Construire des groupes de tests">
            <p>
                Il est peu probable que dans une véritable application on
                ait uniquement besoin de passer un seul scénario de test.
                Cela veut dire que nous avons besoin de grouper les
                scénarios dans un script de test qui peut, si nécessaire,
                lancer tous les tests de l'application.
            </p>
            <p>
                Notre première étape est de créer un nouveau fichier appelé
                <em>tests/all_tests.php</em> et d'y inclure le code suivant...
<php><![CDATA[
<?php
<strong>require_once('simpletest/autorun.php');</strong>

class AllTests extends <strong>TestSuite</strong> {
    function AllTests() {
        $this->TestSuite(<strong>'All tests'</strong>);
        <strong>$this->addFile('log_test.php');</strong>
    }
}
?>
]]></php>
                L'inclusion de &quot;autorun&quot; permettra à notre future suite
                de tests d'être lancée juste en invoquant ce script.
            </p>
            <p>
                La sous-classe <code>TestSuite</code> doit chaîner
                son constructeur. Cette limitation sera supprimée dans
                les versions à venir.   
            </p>
            <p>
                The method <code>TestSuite::addFile()</code>
                will include the test case file and read any new classes
                that are descended from <code>SimpleTestCase</code>.

                Cette méthode <code>TestSuite::addTestFile()</code> va
                inclure le fichier de scénarios de test et lire parmi
                toutes les nouvelles classes créées celles qui sont issues
                de <code>SimpleTestCase</code>.
                <code>UnitTestCase</code> est juste un exemple de classe dérivée
                depuis <code>SimpleTestCase</code> et vous pouvez créer les vôtres.
                <code>TestSuite::addFile()</code> peut aussi inclure d'autres suites.
            </p>
            <p>
                La classe ne sera pas encore instanciée.
                Quand la suite de tests est lancée, elle construira chaque instance
                une fois le test atteint, et le détuira juste ensuite.
                Cela veut dire que le constructeur n'est lancé qu'une fois avant
                chaque initialisation de ce scénario de test et que le destructeur
                est lui aussi lancé avant que le test suivant ne commence.
            </p>
            <p>
                Il est commun de grouper des scénarios de test dans des super-classes
                qui ne sont pas sensées être lancées, mais qui deviennent une classe de base
                pour d'autres tests.
                Pour que &quot;autorun&quot; fonctionne proprement les fichiers
                des scénarios de test ne devraient pas lancer aveuglement
                d'autres extensions de scénarios de test qui ne lanceraient pas
                effectivement des tests.
                Cela pourrait aboutir à un mauvais comptages des scénarios de test
                pendant la procédure.
                Pas vraiement un problème majeure, mais pour éviter cet inconvénient
                il suffit de marquer vos classes de base comme <code>abstract</code>.
                SimpleTest ne lance pas les classes abstraites. Et si vous utilisez encore
                PHP4 alors une directive <code>SimpleTestOptions::ignore()</code>
                dans votre fichier de scénario de test aura le même effet.
            </p>
            <p>
                Par ailleurs, le fichier avec le scénario de test ne devrait pas
                avoir été inclus ailleurs. Sinon aucun scénario de test
                ne sera inclus à ce groupe.
                Ceci pourrait se transformer en un problème plus grave :
                si des fichiers ont déjà été inclus par PHP alors la méthode
                <code>TestSuite::addFile()</code> ne les détectera pas.
            </p>
            <p>
                Pour afficher les résultats, il est seulement nécessaire
                d'invoquer <em>tests/all_tests.php</em> à partir du serveur
                web.
            </p>
            <p>
                Pour plus de renseignements des groupes de tests, voyez le
                <a local="group_test_documentation">documentation sur le groupement des tests</a>.
            </p>
        </section>
        <section name="mock" title="Utiliser les objets fantaisie">
            <p>
                Avançons un peu plus dans le futur.
            </p>
            <p>
                Supposons que notre class logging soit testée et terminée.
                Supposons aussi que nous testons une autre classe qui ait
                besoin d'écrire des messages de log, disons
                <code>SessionPool</code>. Nous voulons tester une méthode
                qui ressemblera probablement à quelque chose comme...
<php><![CDATA[<strong>
class SessionPool {
    ...
    function logIn($username) {
        ...
        $this->_log->message('User $username logged in.');
        ...
    }
    ...
}
</strong>
]]></php>
                Avec le concept de "réutilisation de code" comme fil
                conducteur, nous utilisons notre class <code>Log</code>. Un
                scénario de test classique ressemblera peut-être à...
<php><![CDATA[
<?php
require_once('simpletest/autorun.php');
require_once('../classes/log.php');
<strong>require_once('../classes/session_pool.php');</strong>

class <strong>TestOfSessionLogging</strong> extends UnitTestCase {
    
    function setUp() {
        <strong>@unlink('/temp/test.log');</strong>
    }
    
    function tearDown() {
        <strong>@unlink('/temp/test.log');</strong>
    }
    
    function testLoggingInIsLogged() {
        <strong>$log = new Log('/temp/test.log');
        $session_pool = &new SessionPool($log);
        $session_pool->logIn('fred');</strong>
        $messages = file('/temp/test.log');
        $this->assertEqual($messages[0], "User fred logged in.<strong>\n</strong>");
    }
}
?>
]]></php>
                Nous expliquerons les méthodes <code>setUp()</code>
                et <code>tearDown()</code> plus tard.
            </p>
            <p>
                Le design de ce scénario de test n'est pas complètement
                mauvais, mais on peut l'améliorer. Nous passons du temps à
                tripoter les fichiers de log qui ne font pas partie de
                notre test.
                Pire, nous avons créé des liens de proximité
                entre la classe <code>Log</code> et ce test. Que se
                passerait-il si nous n'utilisions plus de fichiers, mais la
                bibliothèque <em>syslog</em> à la place ?
                
                Cela veut dire que notre test <code>TestOfSessionLogging</code>
                enverra un échec alors même qu'il ne teste pas Logging.
            </p>
            <p>
                Il est aussi fragile sur des petites retouches. Avez-vous
                remarqué le retour chariot supplémentaire à la fin du
                message ? A-t-il été ajouté par le loggueur ? Et si il
                ajoutait aussi un timestamp ou d'autres données ?
            </p>
            <p>
                L'unique partie à tester réellement est l'envoi d'un
                message précis au loggueur.
                Nous pouvons réduire le couplage en
                créant une fausse classe de logging : elle ne fait
                qu'enregistrer le message pour le test, mais ne produit
                aucun résultat. Sauf qu'elle doit ressembler exactement à
                l'original.
            </p>
            <p>
                Si l'objet fantaisie n'écrit pas dans un fichier alors nous
                nous épargnons la suppression du fichier avant et après le
                test. Nous pourrions même nous épargner quelques lignes de
                code supplémentaires si l'objet fantaisie pouvait exécuter
                l'assertion.
            <p>
            </p>
                Trop beau pour être vrai ? Pas vraiement on peut créer un tel
                objet très facilement...
<php><![CDATA[
<?php
require_once('simpletest/autorun.php');
require_once('../classes/log.php');
require_once('../classes/session_pool.php');

<strong>Mock::generate('Log');</strong>

class TestOfSessionLogging extends UnitTestCase {
    
    function testLoggingInIsLogged() {<strong>
        $log = &new MockLog();
        $log->expectOnce('message', array('User fred logged in.'));</strong>
        $session_pool = &new SessionPool(<strong>$log</strong>);
        $session_pool->logIn('fred');
    }
}
?>
]]></php>
                L'appel <code>Mock::generate()</code> a généré
                une nouvelle classe appelé <code>MockLog</code>.
                Cela ressemble à un clone identique, sauf que nous pouvons
                y adjoindre du code de test.
                C'est ce que fait <code>expectOnce()</code>.
                Cela dit que si <code>message()</code> m'est appelé,
                il a intérêt à l'être avec le paramètre
                &quot;User fred logged in.&quot;.
            </p>
            <p>
                L'appel <code>tally()</code> est nécessaire pour annoncer à
                l'objet fantaisie qu'il n'y aura plus d'appels ultérieurs.
                Sans ça l'objet fantaisie pourrait attendre pendant une
                éternité l'appel de la méthode sans jamais prévenir le
                scénario de test. Les autres tests sont déclenchés
                automatiquement quand l'appel à <code>message()</code> est
                invoqué sur l'objet <code>MockLog</code> par le code 
                <code>SessionPool::logIn()</code>.
                L'appel <code>mock</code> va déclencher une comparaison des
                paramètres et ensuite envoyer le message "pass" ou "fail"
                au test pour l'affichage. Des jokers peuvent être inclus
                pour ne pas avoir à tester tous les paramètres d'un appel
                alors que vous ne souhaitez qu'en tester un.
            </p>
            <p>
                Les objets fantaisie dans la suite SimpleTest peuvent avoir
                un ensemble de valeurs de sortie arbitraires, des séquences
                de sorties, des valeurs de sortie sélectionnées à partir
                des arguments d'entrée, des séquences de paramètres
                attendus et des limites sur le nombre de fois qu'une
                méthode peut être invoquée.
            </p>
            <p>
                Pour que ce test fonctionne la librairie avec les objets
                fantaisie doit être incluse dans la suite de tests, par
                exemple dans <em>all_tests.php</em>.
            </p>
            <p>
                Pour plus de renseignements sur les objets fantaisie, voyez le
                <a local="mock_objects_documentation">documentation sur les objets fantaisie</a>.
            </p>
        </section>
        <section name="web" title="Tester une page web">
            <p>
                Une des exigences des sites web, c'est qu'ils produisent
                des pages web. Si vous construisez un projet de A à Z et
                que vous voulez intégrer des tests au fur et à mesure alors
                vous voulez un outil qui puisse effectuer une navigation
                automatique et en examiner le résultat. C'est le boulot
                d'un testeur web.
            </p>
            <p>
                Effectuer un test web via SimpleTest reste assez primitif :
                il n'y a pas de javascript par exemple.
                La plupart des autres opérations d'un navigateur sont simulées.
            </p>
            <p>
                Pour vous donner une idée, voici un exemple assez trivial :
                aller chercher une page web,
                à partir de là naviguer vers la page "about"
                et finalement tester un contenu déterminé par le client.
<php><![CDATA[
<?php
require_once('simpletest/autorun.php');
<strong>require_once('simpletest/web_tester.php');</strong>

class TestOfAbout extends <strong>WebTestCase</strong> {
    function testOurAboutPageGivesFreeReignToOurEgo() {
        <strong>$this->get('http://test-server/index.php');
        $this->click('About');
        $this->assertTitle('About why we are so great');
        $this->assertText('We are really great');</strong>
    }
}
?>
]]></php>
                Avec ce code comme test de recette, vous pouvez vous
                assurer que le contenu corresponde toujours aux
                spécifications à la fois des développeurs et des autres
                parties prenantes au projet.
            </p>
            <p>
                Vous pouvez aussi naviguer à travers des formulaires...
<php><![CDATA[
<?php
require_once('simpletest/autorun.php');
require_once('simpletest/web_tester.php');

class TestOfRankings extends WebTestCase {
    function testWeAreTopOfGoogle() {
        $this->get('http://google.com/');
        $this->setField('q', 'simpletest');
        $this->click("I'm Feeling Lucky");
        $this->assertTitle('SimpleTest - Unit Testing for PHP');
    }
}
?>
]]></php>
                ...même si cela pourrait constituer une violation
                des documents juridiques de Google(tm).
            </p>
            <p>
                Pour plus de renseignements sur comment tester une page web, voyez la
                <a local="web_tester_documentation">documentation sur tester des scripts web</a>.
            </p>
            <p>
                <a href="http://sourceforge.net/projects/simpletest/"><img src="http://sourceforge.net/sflogo.php?group_id=76550&amp;type=5" width="210" height="62" border="0" alt="SourceForge.net Logo"/></a>
            </p>
        </section>
    </content>
    <internal>
        <link>
            <a href="#unit">Utiliser le testeur rapidement</a>
            avec un exemple.
        </link>
        <link>
            <a href="#group">Groupes de tests</a>
            pour tester en un seul clic.
        </link>
        <link>
            <a href="#mock">Utiliser les objets fantaisie</a>
            pour faciliter les tests et gagner en contrôle.
        </link>
        <link>
            <a href="#web">Tester des pages web</a>
            au niveau de l'HTML.
        </link>
    </internal>
    <external>
        <link>
            <a href="https://sourceforge.net/project/showfiles.php?group_id=76550&amp;release_id=153280">Télécharger PHP Simple Test</a>
            depuis <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
        </link>
        <link>
            L'<a href="http://simpletest.org/api/">API de SimpleTest pour développeur</a>
            donne tous les détails sur les classes et assertions existantes.
        </link>
    </external>
    <meta>
        <keywords>
            développement logiciel,
            programmation php,
            outils de développement logiciel,
            tutorial php,
            scripts php gratuits,
            architecture,
            ressources php,
            objets fantaise,
            junit,
            php testing,
            php unit,
            méthodologie,
            développement piloté par les tests,
            sourceforge,
            open source,
            unit test,
            web tester,
            web testing,
            outils tests html,
            tester des web pages,
            php objets fantaise,
            naviguer automatiquement sur des sites web,
            test automatisé,
            scripting web,
            wget,
            test curl,
            jmock pour php,
            jwebunit,
            phpunit,
            php unit testing,
            php web testing,
            jason sweat,
            marcus baker,
            perrick penet,
            topstyle plug in,
            phpedit plug in
        </keywords>
    </meta>
</page>
