Catégorie : Programmation     Tags :      0 Commentaire

    Retrouvez cet article dans : Linux Magazine Hors série 35

    Le développement d’une application implique bien souvent des modifications du code initial (correction d’erreurs, ajout de fonctionnalités, remaniement de code...). Il peut alors être intéressant de mettre en place un mécanisme de tests permettant de vérifier que les nouvelles modifications ne détériorent pas les fonctionnalités existantes. Dans cet article, je vous présente un outil permettant de réaliser cela : PHPUnit, un utilitaire de tests unitaires pour PHP.

    Lorsque l’on développe un programme, il est fréquent de commettre des fautes, des bugs. Si le programme est à l’état de développement initial, il passera forcément, avant sa mise en production, par une série de tests permettant de s’assurer du fonctionnement correct des différentes parties (ou unités) du code. En phase de production, si aucun test n’est effectué, le problème devient beaucoup plus grave : la correction de certains bugs peut avoir des impacts sur d’autres parties du code et entraîner des régressions. La mise en place de tests dès la création d’un programme permet de vérifier tout au cours de l’évolution du code l’absence de telles régressions. Ces tests sont appelés tests unitaires.
    Les notions de base de programmation orientée objet sont nécessaires à la compréhension de cet article. Dans un premier temps, je rappellerai brièvement ces notions. Dans un second temps, je vous présenterai les différentes façons d’écrire des tests unitaires en PHP, comment installer PHPUnit et écrire notre premier test. Ensuite, nous verrons comment tester des programmes contenant du Javascript ou expédiant des mails. Enfin, nous aborderons la couverture de code ou comment tester ses propres tests.

    1. Préambule : petit rappel de théorie objet

    Pour illustrer rapidement les différentes notions de base de la théorie objet, je partirai d’un exemple. Considérons le polygone : il s’agit d’une forme géométrique caractérisée par le nombre de ses sommets. On peut faire de nombreuses opérations sur un polygone, mais, pour notre part, nous nous contenterons de vouloir afficher le nombre de points qui le composent. Le polygone est un objet, une classe en langage de programmation. Pour lui donner une existence, le dessiner en quelque sorte, nous aurons besoin d’une fonction particulière : le constructeur. Chaque élément caractérisant cet objet est appelé attribut (le nombre de points caractérisant un polygone est un attribut). Enfin, toute fonction effectuant une action sur l’objet et liée à celui-ci est appelée méthode. Par exemple, pour afficher le nombre de points qui composent un polygone, nous utiliserons une méthode. Voyons maintenant en PHP comment décrire cela :

    01: class Polygon
    02: {
    03:   private $_points;
    04:
    05:   public function __constructor($pts)
    06:   {
    07:     $this->_points = $pts;
    08:   }
    09:
    10:   public function displayPoints()
    11:   {
    12:     echo ‘Nombre de points : ‘ . $this->_points;
    13:   }
    14: }

    Enfin, la dernière notion essentielle dont il faudra se souvenir est l’héritage. Un carré est, par exemple, un polygone particulier à 4 sommets. On dit qu’il " hérite " du polygone : tous les attributs et toutes les méthodes du polygone sont transmises au carré. En PHP, on écrit :

    01: class Square extends Polygon
    02: {
    03:   public function __constructor()
    04:   {
    05:     parent::__constructor(4);
    06:   }
    07: }

    Ainsi, la méthode displayPoints() définie en ligne 10 de la classe Polygon sera tout de même accessible pour un objet Square.
    Ce rappel étant fait, passons au sujet de cet article : les tests unitaires.

    2. Les tests unitaires en PHP

    Il existe plusieurs façons d’écrire des tests unitaires :

    • affichage d’une valeur de retour d’une fonction et vérification (manuelle ou automatique) ;
    • écriture d’assertions dans le code ;
    • utilisation d’un outil de tests.

    Considérons le code suivant (calculator.class.php) :

    01: <?php
    02:   class Calculator
    03:   {
    04:     public function add($a, $b)
    05:     {
    06:       return $a + $b;
    07:     }
    08:   }
    09: ?>

    Il s’agit d’une classe rudimentaire permettant de faire une addition entre deux nombres. Comme vous pouvez le constater, nous n’avons pas défini de constructeur. Cette classe ne sert qu’à regrouper des méthodes de calcul et nous n’avons aucune initialisation à faire lors de la création de l’objet : nous appellerons donc le constructeur par défaut qui créera un objet vide. Voici un exemple d’utilisation de cette classe (test.php) 

    01: <?php
    02:   include_once ‘calculator.class.php’;
    03:
    04:   $calc = new Calculator();
    05:   echo $calc->add(3, 4);
    06: ?>

    En ligne 4, nous retrouvons l’appel au constructeur par défaut et, en ligne 5, nous appelons la méthode add() sur les nombres 3 et 4 (résultat attendu 7).
    Nous allons maintenant développer les différentes méthodes de tests unitaires sur cet exemple.

    2.1. Affichage simple

    Comme son nom l’indique, l’affichage simple consiste à afficher une valeur permettant de vérifier le comportement des fonctions. La modification (en rouge) s’effectue directement sur le fichier que l’on désire tester (ici test.php).

    01: <?php
    02:   include_once ‘calculator.class.php’;
    03:
    04:   $calc = new Calculator();
    05:   if ($calc->add(3, 4) == 7)
    06:   {
    07:     echo ‘pass’;
    08:   }
    09:   else
    10:   {
    11:     echo ‘failed’;
    12:   }
    13: ?>

    La rédaction de tels tests, outre le fait qu’elle soit fastidieuse, entraîne rapidement un obscurcissement du code. Les lignes 5 à 12 ne servent qu’à tester le bon fonctionnement de la fonction add() de la classe calculator. Lors de la mise en production, ces lignes devront être supprimées ou commentées.

    2.2. Assertions

    Une autre solution consiste à utiliser les assertions au travers du mécanisme fourni par les fonctions de la famille assert(). Toutefois, il faudra tenir compte des avertissements fournis dans le manuel PHP [1] :
    " Il est recommandé de n’utiliser les assertions que comme outil de débogage. Vous pouvez les utiliser pour les vérifications d’usage : ces conditions doivent normalement être vraies, et indiquer une erreur de programmation si ce n’est pas le cas. Vous pouvez aussi vérifier la présence de certaines extensions ou limitations du système. Les assertions ne doivent pas être utilisées pour faire des opérations de vérifications en production, comme des vérifications de valeur d’argument. En conditions normales, votre code doit être en état de fonctionner si la vérification d’assertion est désactivée. "
    Là encore, il faut modifier le fichier que l’on désire tester (test.php).

    01: <?php
    02:   include_once ‘calculator.class.php’;
    03:
    04:   assert_options(ASSERT_ACTIVE, 1);
    05:   assert_options(ASSERT_WARNING, 1);
    06:
    07:   $calc = new Calculator();
    08:   assert($calc->add(3, 4) == 7);
    09: ?>

    Cette fois-ci seulement 3 lignes ont été nécessaires pour créer le test : les lignes 4 et 5 qui permettent de définir les options d’utilisation de la fonction assert() et la ligne 8 qui effectue l’assertion proprement dite. Ce mécanisme peut être beaucoup plus puissant en utilisant une fonction utilisateur rendant compte des échecs d’assertions (voir l’option ASSERT_CALLBACK de assert_options() [2]).
    Pour désactiver les assertions, il suffit de modifier la ligne 4 en désactivant l’option ASSERT_ACTIVE :

        assert_options(ASSERT_ACTIVE, 0);

    Cette méthode, bien que plus robuste que la précédente, n’en présente pas moins le même défaut : code et tests sont inextricablement mêlés ce qui rend la maintenance de l’un comme de l’autre beaucoup plus délicate.

    2.3. Outils de tests

    Pour pouvoir produire des tests aisément maintenables et réutilisables, l’utilisation d’outils de tests est indispensable. Ces outils doivent pouvoir répondre aux besoins suivants :

    • séparation stricte du code et des tests ;
    • codage des tests simple et intuitif ;
    • possibilité d’exécuter automatiquement les tests ;
    • production d’une analyse des résultats des tests.

    Trois outils répondent à ces critères:

    • phpt : il s’agit du système utilisé par PHP lui-même pour " s’auto-tester ". Cet outil est simple à mettre en œuvre, mais reste assez rudimentaire. Voici un exemple de la syntaxe employée pour écrire les tests (fichier test.phpt) :
    01: --TEST--
    02: Test de la fonction strlen()
    03: --FILE--
    04: <?php
    05:   $_var = ‘abcdef’;
    06:   echo ‘Longueur = ‘ . strlen($_var);
    06: ?>
    07: --EXPECT--
    08: Longueur = 6

    Vous pourrez trouver des explications plus détaillées sur le site de l’équipe de développement de phpt [3].

    • SimpleTest : il s’agit d’une solution complète de tests pour PHP [4]. Parmi les lacunes de cet outil, on peut noter que son code n’a toujours pas migré vers PHP5 et que les tests de code AJAX ne sont pas directement intégrés : il faut installer une extension disponible dans le CVS du projet [5].
    • PHPUnit : une autre solution complète de tests pour PHP [6] qui, elle, intègre nativement les tests de code AJAX. C’est à cet outil que nous allons nous intéresser dans la suite de cet article.

    3. Installation de PHPUnit

    Le projet PHPUnit est hébergé par PEAR et l’installation se fait très facilement grâce au système " pear install ". Vous devrez tout d’abord vous assurer que vous possédez bien le paquetage pear grâce à la commande pear help. Vous devez voir apparaître la liste des commandes de PEAR.
    Dans le cas contraire, le paquetage n’est pas installé (pour les distributions basées sur Debian, il suffira d’exécuter apt-get install php-pear).
    Il faut ensuite indiquer à PEAR où se trouvent les fichiers de PHPUnit :

    pear channel-discover pear.phpunit.de

    L’installation se fait alors à l’aide de l’instruction suivante :

    pear install --alldeps phpunit/PHPUnit

    Si vous aviez déjà installé PHPUnit, vous pouvez vérifier que vous possédez bien la dernière version grâce à la commande pear list-upgrades qui affiche la liste des paquetages PEAR installés sur votre système, ainsi que leur version et la dernière version disponible. Si votre version n’est pas à jour, exécutez pear upgrade phpunit/PHPUnit.
    Il est possible que vous rencontriez des problèmes lors de l’installation. PEAR peut vous renvoyer un message du type :

    Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 15 bytes) in /usr/share/php/PEAR/Registry.php on line 1053

    Il faut alors modifier la valeur du memory_limit dans php.ini. Mais attention ! Il y a deux fichiers php.ini : l’un est utilisé par Apache et l’autre est utilisé par PHP CLI lors de l’utilisation de PHP en mode ligne de commande (pour en savoir plus, voir [7]). C’est dans ce dernier fichier (/etc/php5/cli/php.ini sur les distributions basées sur Debian) qu’il faudra modifier la valeur du memory_limit par :

    memory_limit = 32M

    Pour information, à partir de PHP 5.2.3, pour connaître le fichier php.ini utilisé par PHP CLI, vous pouvez taper php --ini qui devrait renvoyer un affichage de la forme :

    Configuration File (php.ini) Path: /etc/php5/cli
    Loaded Configuration File:         /etc/php5/cli/php.ini
    Scan for additional .ini files in: (none)
    Additional .ini files parsed:      (none)

    L’installation étant maintenant achevée, nous pouvons nous lancer dans les tests unitaires.

    4. Premier test avec PHPUnit

    Pour retrouver aisément les tests associés à chaque classe, nous utiliserons la nomenclature suivante :

    • La classe de tests associée à une classe MaClasse s’appellera MaClasseTest (et pour les fichiers, la classe MaClasse sera contenue dans maclasse.class.php et la classe MaClasseTest sera contenue dans maclassetest.class.php).
    • Toute méthode maMethode de la classe MaClasse sera testée dans une méthode testMaMethode dans la classe MaClasseTest.

    Nous allons maintenant commencer par créer une classe de tests très simple pour notre classe Calculator donnée en exemple en section 2.

    4.1. Écriture du test

    Reprenant le fichier calculator.class.php. Nous allons créer, dans le même répertoire, un fichier calculatortest.class.php.

    01: <?php
    02:   require_once ‘PHPUnit/Framework.php’;
    03:   require_once ‘calculator.class.php’;
    04:
    05:   class CalculatorTest extends PHPUnit_Framework_TestCase
    06:   {
    07:     protected $fixture;
    08:
    09:     protected function setUp()
    10:     {
    11:       $this->fixture = new Calculator();
    12:     }
    13:
    14:     public function testAdd()
    15:     {
    16:       $this->assertEquals(3, $this->fixture->add(2, 1));
    17:       $this->assertNotEquals(7, $this->fixture->add(1, 5));
    18:     }
    19:   }
    20: ?>

    Essayons de comprendre la structure de ce test. En début de fichier, nous avons :

    • la ligne 2 qui permet le chargement de la bibliothèque PHPUnit : elle est, bien sûr, obligatoire et se retrouvera dans tous nos tests ;
    • La ligne 3 qui charge la classe que nous voulons tester.

    Ensuite, dans les lignes 5 à 18, nous abordons la classe de test proprement dite :

    • La ligne 5 déclare la classe de test CalculatorTest qui hérite de la classe PHPUnit_Framework_TestCase (ce sera le cas de la plupart des tests que nous créerons).
    • En ligne 7, nous déclarons un attribut fixture (je reprends ici pour plus de clarté le terme employé dans la documentation de PHPUnit [8]) dont la portée sera de type protected (accessible dans la classe CalculatorTest et dans les classes dérivées). Nous nous servirons de cet attribut pour créer un objet Calculator.
    • Les lignes 9 à 12 définissent la méthode setUp(). Cette méthode permet d’exécuter une partie de code au démarrage du test de la classe. Ce code est en général commun à toutes les méthodes de test de la classe (ici, il s’agit de l’objet Calculator qui pourrait ainsi être utilisé dans toutes les méthodes de test sans avoir à le redéfinir). La variable $this fait référence à un objet PHPUnit.
    • Les lignes 14 à 18 définissent la méthode testAdd() permettant de tester la méthode add() de la classe Constructor. Nous utilisons ici deux types de tests au travers des méthodes d’assertion assertEquals() et assertNotEquals() de PHPUnit. En ligne 16, on vérifie que le résultat renvoyé par la méthode add(2, 1) est bien 3. De même, en ligne 17, on vérifie que la méthode add(1, 5) ne renvoie pas 7.

    4.2. Lancement du test en mode console

    Pour lancer le test, dans une console (et dans le même répertoire que nos fichiers), tapez :

    phpunit CalculatorTest calculatortest.class.php

    Le premier paramètre correspond au nom de la classe de test et le second paramètre correspond au fichier implémentant ladite classe (ce paramètre est optionnel ; s’il est omis, la classe sera recherchée dans un fichier portant le même nom que la classe et suivi de l’extension .php). Le résultat obtenu est le suivant :

    PHPUnit 3.2.11 by Sebastian Bergmann.
    .
    Time: 0 seconds
    OK (1 test)

    Durant l’exécution de PHPUnit en ligne de commande, un caractère indiquant le résultat de chaque test est affiché (dans notre exemple, il s’agit du point affiché au-dessus de la ligne " Time... "). Le tableau suivant indique les différents caractères possibles, ainsi que leur signification.

     /img-articles/lmhs/35/cc-art-php1/t1.jpg

    Nous venons de voir l’exécution simple de PHPUnit en mode console, mais il existe de nombreuses options (pour toutes les afficher : phpunit --help). Je vais maintenant présenter celles qui me paraissent les plus utiles.

    4.2.1. Représentation graphique des résultats

    PHPUnit intègre la possibilité de générer un graphique des résultats au format GraphViz [9]. Ce graphique permettra d’appréhender rapidement les différents liens entre classes et surtout l’ensemble des classes pour lesquelles au moins un test a échoué. En utilisant le paramètre --log-graphviz, PHPUnit génèrera un fichier descriptif de diagramme GraphViz au format .dot.

    phpunit --log-graphviz Calculator.dot CalculatorTest calculatortest.class.php

    Ce fichier ne sera visionnable que si vous avez installé le paquetage graphviz sur votre système (sous Debian apt-get install graphviz). Grâce à la commande dot, nous allons générer un fichier .png du graphique.

    dot -T png -o Calculator.png Calculator.dot

    En figure 1 (page suivante), vous pourrez voir le graphe obtenu pour notre exemple. Les tests réussis sont représentés avec une bordure verte, les échecs et erreurs avec une bordure rouge, les tests incomplets ou ignorés avec une bordure jaune.

    /img-articles/lmhs/35/cc-art-php1/fig-1.jpg

    Figure 1 : Graphe de résultats des tests pour la classe Calculator

    4.2.2. Résultats des tests sous forme XML

    Il est possible de générer un fichier XML des résultats des tests. Pour cela, il faut utiliser le paramètre --log-xml suivi du nom du fichier XML dans lequel on désire stocker le résultat. À partir de notre exemple, la génération du fichier XML se fait en invoquant :

    phpunit --log-xml Calculator.xml CalculatorTest calculatortest.class.php

    Le fichier XML créé (Calculator.xml) est le suivant :

    01: <?xml version = “1.0” encoding = “UTF-8”?>
    02: <testsuites>
    03:   <testsuite name = “CalculatorTest” file = “calculatortest.class.php” tests = “1”
    04:              failures = “0” errors = “0” time = “0.001111”>
    05:     <testcase name = “testAdd” class = “CalculatorTest”
    06:               file = “calculatortest.class.php” line = “15” time=”0.001111” />
    07:   </testsuite>
    08: </testsuites>

    La balise <testsuites> définit un ensemble de suites de tests, notion que nous aborderons à la section suivante. À ce stade, ce qui nous importe est le fait que notre fichier de test, nommé calculatortest.class.php, ait été exécuté (ligne 3). Ce fichier comportait un test et il n’y a eu aucune erreur comme en témoignent les lignes 3 et 4. Le seul test présent dans ce fichier est rapporté dans le tag <testcase> et il s’agit du test que nous avions nommé testAdd (voir lignes 5 et 6). Si le test avait échoué, il aurait été signalé par une balise <failure> indiquant le type d’erreur. Cette balise aurait été incluse dans la balise <testcase> correspondant au test ayant échoué.

    L’intérêt d’une telle représentation n’est pas bien sûr de lire le code XML tel quel. À partir de ce fichier, en appliquant une feuille de style XSL, nous pouvons générer n’importe quel format d’affichage. Voici un exemple de génération de fichier HTML grâce au fichier de style xml2html.xsl :

    01: <?xml version = “1.0” encoding = “iso-8859-15”?>
    02: <xsl:stylesheet xmlns:xsl = “http://www.w3.org/1999/XSL/Transform” version = “1.0”>
    03: <xsl:output
    04:   indent               = “yes”
    05:   method               = “xml”
    06:   doctype-system       =
    07:     “http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-strict.dtd”
    07:   doctype-public       = “-//W3C//DTD XHTML 1.0 Strict//EN”
    08:   omit-xml-declaration = “yes”
    09:   encoding             = “iso-8859-15” />
    10:
    11: <xsl:template match = “/testsuites”>
    12:   <html>
    13:     <head>
    14:       <meta http-equiv = “Content-Type”
    15:             content = "text/HTML; charset = iso-8859-15" />
    16:       <title>Résultat des test unitaires</title>
    17:       <style type = “text/css”>
    18:         @import url(‘phpunit.css’);
    19:       </style>
    20:     </head>
    21:     <body>
    22:       <xsl:apply-templates select = “testsuite” />
    23:     </body>
    24:   </html>
    25: </xsl:template>
    26:
    27: <xsl:template match = “testsuite”>
    28:   <h1>Test : <xsl:value-of select = “@name” /> (réalisé en
    29:       <xsl:value-of select = “@time” />s)</h1>
    30:   <table>
    31:     <tr><td>Nombre de tests</td><td><xsl:value-of select = "@tests" /></td></tr>
    32:     <tr><td>Nombre d’assertions ayant échoué</td><td>
    33:       <xsl:value-of select = “@failures” /></td></tr>
    34:      <tr><td>Nombre d’erreurs</td><td><xsl:value-of select = "@errors" /></td></tr>
    35:   </table>
    36:
    37:   <xsl:apply-templates select = “testcase”/>
    38: </xsl:template>
    39:
    40: <xsl:template match = “testcase”>
    41:   <h2><xsl:value-of select = “@name” /> (<xsl:value-of select = “@time” />s)</h2>
    42:   <xsl:apply-templates select = “failure” />
    43: </xsl:template>
    44:
    45: <xsl:template match = “failure”>
    46:   <h3>ERREUR : <xsl:value-of select = “@type” /></h3>
    47:   <xsl:value-of select = “./text()” />
    48: </xsl:template>
    49:
    50: </xsl:stylesheet>

    Dans les lignes 2 à 9, nous indiquons que nous allons générer un document XHTML suivant le doctype XHTML 1.0 Strict. Ensuite, les lignes 11 à 25, 27 à 35, 40 à 43, et 45 à 48 définissent respectivement les règles de remplacement pour les tags <testsuites>, <testsuite>, <testcase> et <failure>.

    L’affichage de notre fichier HTML est amélioré à l’aide de la feuille de style phpunit.css :

    01: table
    02: {
    03:   background: #9FE;
    04: }
    05:
    06: h2
    07: {
    08:   color: #00F;
    09: }
    10:
    11: h3
    12: {
    13:   color: #F00;
    14: }
    Nous utiliserons la commande xsltproc permettant d’appliquer une feuille de style XSL à un document XML :
    xsltproc -o Calculator.html xml2html.xsl Calculator.xml

    Le résultat obtenu est présenté en figure 2.

    /img-articles/lmhs/35/cc-art-php1/fig-2.jpg

    Figure 2 : Page de résultats sous forme HTML après application d’une feuille de style XSL

    4.2.3. Génération de documentation

    Il est possible de générer une documentation de vos tests au format texte ou html. Cette fonctionnalité assez simple va analyser tous les noms de méthodes d’une classe de tests et les convertir en phrases (en séparant les mots par rapport aux caractères majuscules). Par exemple, pour une méthode testCeciEstUneDescriptionDeLaMethode(), nous obtenons la phrase " Ceci est une description de la méthode ". De tels fichiers de documentation sont générés à l’aide du paramètre --testdox-text pour les fichiers texte et --testdox-html pour les fichiers html. Pour créer la documentation à partir de notre exemple :

     phpunit --testdox-text Calculator.txt CalculatorTest calculatortest.class.php

    Le fichier généré (Calculator.txt) ne sera pas très utile, car le nom de la seule méthode présente n’est pas descriptif. Mais, suivant les cas, un tel mécanisme peut aider à relire rapidement d’anciens tests, partager aisément les tests avec d’autres développeurs, etc.

    4.2.4. Génération du squelette des tests d’une classe

    On appelle " squelette d’un fichier " le fichier minimal contenant les déclarations de classes et de fonctions non complétées. Pour générer automatiquement le squelette du fichier de tests d’une classe, il faut invoquer PHPUnit avec le paramètre --skeleton. Sur notre exemple de classe Calculator, la génération du squelette se fait par :

     phpunit --skeleton Calculator calculator.class.php

    Le fichier de test s’appellera CalculatorTest.php du nom de notre classe suivi de Test.php.
    On peut noter ici qu’un mécanisme d’annotations permettant de simplifier l’écriture de tests élémentaires a été mis en place. Il s’agit de l’annotation @assert à positionner dans des balises de commentaires de votre fichier initial et qui, lors de la génération du squelette, seront transformées en test. Modifions le code de notre exemple calculator.class.php de la manière suivante (les modifications sont indiquées en rouge) :

    01: <?php
    02:   class Calculator
    03:   {
    04:     /**
    05:      * @assert (5, 2)  == 7
    06:      * @assert (-1, 1) == 0
    07:      */
    08:     public function add($a, $b)
    09:     {
    10:       return $a + $b;
    11:     }
    12:   }
    13: ?>

    La ligne 5 permet d’indiquer au générateur du squelette qu’un des tests de la fonction add() sera créé en testant la valeur de retour de cette fonction lorsque les paramètres " 5 " et " 2 " lui sont passés en arguments. Le résultat devra être " 7 ". De même, en ligne 6, on indique que le résultat de add(-1, 1) devrait être " 0 ". On peut créer le squelette pré-rempli du fichier de tests en utilisant à nouveau la commande suivante (pensez à effacer le fichier calculator.class.php précédemment généré sous peine de conserver l’ancienne version) :

     phpunit --skeleton Calculator calculator.class.php

     Voici les fonctions de test générées extraites du fichier CalculatorTest.php :

    01: /**
    02:  * Generated from @assert (5, 3) == 8.
    03:  */
    04: public function testAdd()
    05: {
    06:   $this->assertEquals(
    07:     8,
    08:     $this->object->add(5, 3)
    09:   );
    10: }
    11:
    12: /**
    13:  * Generated from @assert (1, 0) == 1.
    14:  */
    15: public function testAdd2()
    16: {
    17:   $this->assertEquals(
    18:     1,
    19:     $this->object->add(1, 0)
    20:   );
    21: }

    Les différentes assertions que vous pourrez utiliser dans les balises @assert sont résumées dans le tableau ci-dessous.

    /img-articles/lmhs/35/cc-art-php1/t2.jpg

     Si vous avez attentivement lu le code généré, vous avez dû vous apercevoir que de nombreuses lignes ont été ajoutées par rapport au fichier que nous avons utilisé en exemple. Lorsque PHPUnit génère le squelette des tests d’une classe, il génère une " suite de tests ". Je développerai ce concept dans la section suivante.

    5. Construire une suite de tests

    Il peut être un peu rébarbatif d’avoir à lancer manuellement le test associé à chaque classe d’un projet. On peut, heureusement, effectuer une composition de tests et, par exemple, lancer tous les tests couvrant un projet, ceux des classes qui composent un des modules du projet ou encore ceux d’une seule classe. Le mécanisme permettant de réaliser cela est la suite de tests.
    Pour créer une suite de tests d’après notre précédent exemple calculatortest.class.php, nous allons devoir le modifier très légèrement (voir les lignes en rouge) :

    01: #!/usr/bin/php -q
    02: <?php
    03:   require_once ‚PHPUnit/Framework.php‘;
    04:   require_once ‚calculator.class.php‘;
    05:
    06:   if (!defined(‘PHPUnit_MAIN_METHOD’))
    07:   {
    08:     define(‘PHPUnit_MAIN_METHOD’, ‘CalculatorTest::main’);
    09:   }
    10:
    11:   class CalculatorTest extends PHPUnit_Framework_TestCase
    12:   {
    13:     protected $calc;
    14:
    15:     public static function main()
    16:     {
    17:       require_once ‘PHPUnit/TextUI/TestRunner.php’;
    18:
    19:       $suite  = new PHPUnit_Framework_TestSuite(‘CalculatorTest’);
    20:       PHPUnit_TextUI_TestRunner::run($suite);
    21:     }
    22:
    23:     protected function setUp()
    24:     {
    25:       $this->fixture = new Calculator();
    26:     }
    27:
    28:     public function testAdd()
    29:     {
    30:       $this->assertEquals(3, $this->fixture->add(2, 1));
    31:     }
    32:   }
    33:
    34:   if (PHPUnit_MAIN_METHOD == ‘CalculatorTest::main’)
    35:   {
    36:     CalculatorTest::main();
    37:   }
    38:
    39: ?>

    Voici le détail des modifications :

    • La première modification porte sur la ligne 1 : ce script est écrit en PHP-CLI (PHP en ligne de commande). Pour l’exécuter, il suffira de rendre le fichier exécutable (chmod u+x calculatortest.class.php), puis d’invoquer calculatortest.class.php ;
    • Dans les lignes 6 à 9, on définit la première méthode qui sera exécutée si le fichier est appelé directement. Cette définition sera ignorée si la classe de test est incluse dans une autre suite.
    • Les lignes 15 à 21 définissent la méthode statique main() qui fournit une implémentation de la suite (ligne 19). À ce niveau, on pourrait inclure d’autres tests ou d’autres suites grâce aux méthodes addTest() et addSuite() comme nous le verrons dans le script suivant.

    Enfin, la ligne 20 permet de lancer le test si le fichier n’est pas inclus dans une autre suite.

    • Les lignes 34 à 37 servent, quant à elles, à lancer la méthode main() si le fichier est appelé directement.

    Si vous avez étudié précédemment le fichier généré par PHPUnit lors de la création du squelette des tests d’une classe (section 3.2.4), vous devez maintenant voir que c’est une suite de tests qui a été générée, ce qui vous permettra de créer un fichier unique avec une suite qui contiendra toutes les autres suites et pourra lancer tous les tests en une seule action.
    Ce script, permettant de lancer tous les tests depuis la racine du répertoire contenant les scripts de tests unitaires de notre projet, se nommera AllTests.php. Nous le placerons dans une architecture permettant d’archiver proprement le code et les tests. En figure 3, je vous présente un exemple d’organisation de ces  fichiers. Ce n’est qu’une architecture parmi d’autres, l’important est de s’assurer qu’une telle structuration existe et surtout de la respecter.

    /img-articles/lmhs/35/cc-art-php1/fig-3.jpg
    Figure 3 : Exemple d’organisation des fichiers d’un projet pour séparer le code et les tests

    L’écriture du script AllTests à l’aide de la classe PHPUnit_Framework_TestSuite sera essentielle, car c’est ce script qui pilotera le lancement de tous les tests :

    01: #!/usr/bin/php -q
    02: <?php
    03:
    04:   require_once ‘PHPUnit/Framework.php’;
    05:   require_once ‘PHPUnit/TextUI/TestRunner.php’;
    06:
    07:   require_once ‘calculatortest.class.php’;
    08:
    09:   if (!defined(‘PHPUnit_MAIN_METHOD’))
    10:   {
    11:     define(‘PHPUnit_MAIN_METHOD’, ‘AllTests::main’);
    12:   }
    13:
    14:   class AllTests
    15:   {
    16:     public static function main()
    17:     {
    18:       PHPUnit_TextUI_TestRunner::run(self::suite());
    19:     }
    20:
    21:     public static function suite()
    22:     {
    23:       $suite = new PHPUnit_Framework_TestSuite(‘All Tests’);
    24:       $suite->addSuite(CalculatorTest);
    25:     }
    26:   }
    27:
    28:   if (PHPUnit_MAIN_METHOD == ‘AllTests::main’)
    29:   {
    30:     AllTests::main();
    31:   }
    32:
    33: ?>

    Comme le montre la ligne 1, ce script est lui aussi écrit en PHP-CLI. Pour l’exécuter, il suffira de rendre le fichier exécutable (chmod u+x AllTests), puis d’invoquer AllTests.

    Voici quelques explications sur les autres lignes du script :

    • En ligne 7, on charge le fichier correspondant aux tests que l’on veut lancer (c’est à ce niveau qu’il faudra également intégrer chaque classe de tests que l’on voudra lancer) ;
    • De la ligne 14 à la ligne 26, on définit une classe AllTests qui permettra l’exécution de l’ensemble des tests. Pour cela, la fonction main() des lignes 16 à 19 lance l’exécution des tests définis dans la fonction suite() (lignes 21 à 25). En ligne 24, on ajoute la suite CalculatorTest. Si l’on avait voulu ajouter seulement l’un des tests de cette suite, on aurait pu écrire $suite->addTest(CalculatorTest::testAdd());

    Par ce mécanisme, on peut ainsi ajouter tous les tests et toutes les suites que l’on désire à notre script.

    6. Les petits plus de PHPUnit

    6.1. PHPUnit et Ajax

    Avec l’avènement du Web dit " 2.0 ", de plus en plus d’applications PHP utilisent du code Javascript pour offrir un meilleur confort d’utilisation. Pour pouvoir exécuter du code Javascript depuis PHPUnit, nous aurons besoin d’installer Selenium RC [10], un outil permettant d’écrire des tests automatisés sur un navigateur implémentant Javascript (simulation du comportement d’un utilisateur à l’aide de scenarii). Ces tests peuvent être déployés à la fois pour les tests de recette (confirmation que l’application répond correctement aux spécifications du cahier des charges) et les tests de compatibilité de navigateurs (en testant l’application sur différents systèmes d’exploitation et navigateurs).

    L’installation se déroule en trois étapes :

    1. Récupérez les fichiers de Selenium RC directement sur le site http://www.openqa.org/selenium-rc/ ou à l’aide de la commande :

    	  wget http://release.openqa.org/selenium-remote-control/0.9.2/
                  selenium-remote-control-0.9.2-dist.zip

    2. Décompressez l’archive :

    	  unzip selenium-remote-control-0.9.2-dist.zip

    Vous obtiendrez un répertoire selenium-remote-control-0.9.2/.

    3. En tant que root, effectuez :

      cp selenium-remote-control-0.9.2/selenium-server-0.9.2/selenium-server.jar
      /usr/local/bin

    Pour lancer le serveur Selenium RC, vous n’aurez plus qu’à exécuter :

    	java -jar /usr/local/bin/selenium-server.jar

    Pour plus de simplicité, vous pouvez créer un alias dans le fichier de configuration de votre shell (~/.bashrc pour un shell bash) en ajoutant la ligne :

    	alias selenium=’java -jar /usr/local/bin/selenium-server.jar’

    N‘oubliez pas de lancer un source ~/.bashrc pour activer votre modification.

    Pour tester notre installation, nous allons utiliser le scénario suivant : nous voulons connaître le nom de la page générée à la suite d’une recherche sur Google avec les mots clés " Linux " et " Magazine ". Nous n’exploiterons pas ici de liaison Javascript – PHP (Ajax), mais la compréhension de cet exemple devrait vous permettre de le faire aisément par la suite.

    Nous commençons par lancer Selenium RC en mode interactif :

    	java -jar /usr/local/bin/selenium-server.jar -interactive

    Nous pouvons alors demander au serveur de se connecter à Google en utilisant Firefox comme navigateur :

    	cmd=getNewBrowserSession&1=*firefox&2=http://www.google.fr

    Selenium RC devrait alors lancer Firefox et vous fournir dans le terminal un identifiant de session pour votre connexion au site http://www.google.com.

    	Got result: OK,106428 on session 106428

    Récupérez cet identifiant (106428 dans notre exemple) et lancez la commande :

    	cmd=open&1=http://www.google.fr&sessionId=106428

    Vous devriez alors obtenir l’écran présenté en figure 4 (page suivante).

     /img-articles/lmhs/35/cc-art-php1/fig-4.jpg

    Figure 4 : Google.fr démarré depuis Selenium RC sous Firefox

    On peut alors entrer en interaction avec le site sur lequel nous nous sommes connectés. Dans le cadre de notre exemple, nous rechercherons le site de GNU//Linux Magazine en complétant le champ de texte Google :

    	cmd=type&1=q&2=Linux Magazine&sessionId=106428

    Puis, nous cliquons sur le bouton " Recherche Google " (où " btnG " est l’attribut name du bouton dans le code HTML de la page) :

    	cmd=click&1=btnG&sessionId=106428

    Nous obtenons alors l’écran présenté en figure 5.

     /img-articles/lmhs/35/cc-art-php1/fig-5.jpg

    Figure 5 : Résultat de la recherche " Linux Magazine " sur Google.fr démarré depuis Selenium RC sous Firefox.

    Nous pouvons récupérer alors le titre de la page par :

    	cmd=getTitle&sessionId=106428

    Le résultat apparaîtra dans le terminal sous la forme :

    	Got result: OK,Linux Magazine - Recherche Google on session 106428

    Notre test étant achevé, nous pouvons clore proprement notre session de connexion par :

    	cmd=testComplete&sessionId=106428

    Puis, nous quittons Selenium RC par [Ctrl]-C. Cette démonstration illustre les possibilités de Selenium RC qui peuvent être exploitées directement depuis PHPUnit. Nous allons maintenant voir comment réaliser cela en reprenant notre exemple de recherche Google dans un fichier que nous nommerons googletest.class.php.

    01: <?php
    02:   require_once ‘PHPUnit/Extensions/SeleniumTestCase.php’;
    03:
    04:   class GoogleTest extends PHPUnit_Extensions_SeleniumTestCase
    05:   {
    06:     protected function setUp()
    07:     {
    08:       $this->setBrowser(‘*firefox’);
    09:       $this->setBrowserUrl(‘http://www.google.fr/’);
    10:     }
    11:
    12:     public function testTitle()
    13:     {
    14:       $this->open(‘http://www.google.fr/’);
    15:       $this->type("q", "Linux Magazine");
    16:       $this->click("btnG");
    17:       $this->waitForPageToLoad(10000);
    18:       $this->assertRegExp("/Linux Magazine - Recherche Google/", $this->getTitle());
    19:     }
    20:   }
    21: ?>

    Vous reconnaîtrez tout au long de ce code les commandes que nous avons précédemment lancées dans le mode interactif de Selenium. Nous utilisons l’extension SeleniumTestCase (lignes 2 et 4) pour pouvoir interagir avec Selenium depuis PHPUnit.

    La fonction setUp() (lignes 6 à 10) définit le navigateur à utiliser et l’URL de connexion.

    Dans les lignes 12 à 19, la fonction testTitle() reproduit les commandes du mode interactif de Selenium en ajoutant un délai d’attente pour le chargement de la page des résultats (ligne 17). Le test que nous avons introduit en ligne 18 permet de s’assurer que le titre de la page résultante est bien Linux Magazine – Recherche Google.

    Pour plus de détails sur les fonctions disponibles pour Selenium dans l’API PHPUnit, reportez-vous au chapitre qui y est consacré dans la documentation de PHPUnit [8].

    Pour exécuter ce test, nous devons au préalable lancer le serveur Selenium dans une console :
    selenium
    Une fois le serveur lancé, le test est exécuté par la commande classique :
    phpunit GoogleTest googletest.class.php
    Vous verrez alors s’ouvrir le navigateur, la recherche Google sera effectuée et le résultat du test s’affichera dans le terminal.

    6.2. Les tests d’envois de mails

    Lors du développement d’une application, il peut être utile de s’assurer que le contenu des mails expédiés correspond bien au cahier des charges. En effet, si votre application envoie automatiquement des commandes par mail, une erreur sur la quantité commandée pourrait, par exemple, avoir de graves répercussions.

    Pour tester l’envoi des mails, nous devrons installer Fakemail, un petit utilitaire qui va capturer les mails au moment de l’expédition et va les sauvegarder dans un fichier texte.

    Téléchargez le fichier fakemail_1.0_beta.tar.gz sur http://sourceforge.net/projects/fakemail/.

    Nous décompresserons ensuite le fichier dans /opt en tant que root :

    mv fakemail_1.0_beta.tar.gz /opt
    cd /opt
    tar -zxvf fakemail_1.0_beta.tar.gz
    rm fakemail_1.0_beta.tar.gz

    Ceci nous crée un répertoire fakemail/ qui contient les fichiers de Fakemail.
    Fakemail est écrit en Perl. Il faut donc s’assurer que ce dernier est correctement installé. De plus, vous aurez besoin du module Net::Server::Mail::SMTP.pm. Pour les distributions basées sur Debian, il vous suffira de lancer :

    apt-get install perl libnet-server-mail-perl

    Pour les autres distributions, vous pourrez trouver le module sur le site du CPAN http://www.cpan.org.
    Pour pouvoir lancer Fakemail plus facilement, vous pouvez, là encore, créer un alias dans le fichier de configuration de votre shell en ajoutant la ligne (sans oublier, s’il s’agit du fichier ~/.bashrc, d’exécuter source ~/.bashrc) :

     	alias fakemail=’/opt/fakemail/fakemail’

    L’utilisation de Fakemail nécessite de changer de port SMTP. Pour cela, il faut installer la classe Mail de PEAR (en effet, la fonction native mail() de PHP ne le permet pas) :

    	pear install --alldeps Mail

    Exemple pour tester l’envoi des mails (fichier mail.php) :

    01: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    02:   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    03: <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
    04:   <head>
    05:     <title>Test Mail</title>
    06:   </head>
    07:
    08:   <body>
    09:     <?php
    10:       require_once ‘Mail.php’;
    11:
    12:       if (isset($_GET[‘email’]))
    13:       {
    14:         $_recipients         = trim($_GET[‘email’]);
    15:
    16:         $_headers[‘From’]    = ‘test@testeur.com’;
    17:         $_headers[‘To’]      = $_recipients;
    18:         $_headers[‘Subject’] = ‘Petit test de mail’;
    19:
    20:         $_body               = ‘Salut !’;
    21:
    22:         $_params[‘host’]     = ‘localhost’;
    23:         $_params[‘port’]     = 10025;
    24:
    25:         $_mail =& Mail::factory(‘smtp’, $_params);
    26:
    27:         if ($_mail->send($_recipients, $_headers, $_body))
    28:         {
    29:           echo ‘Mail envoyé à <em>’ . $_GET[‘email’] . ‘</em><br />’;
    30:         }
    31:       }
    32:     ?>
    33:     <form method = “get” action = “mail.php”>
    34:       <fieldset>
    35:         <legend>Mail</legend>
    36:         Donnez votre adresse mail:<br />
    37:         <input type = “text” name = “email” /><br />
    38:         <input type = “submit” name = “send” />
    39:       </fieldset>
    40:     </form>
    41:   </body>
    42:
    43: </html>

    On peut tester si le code fonctionne en lançant Fakemail dans un terminal en écoute sur le port 10025 par :

    	fakemail --host=localhost --port=10025 –path=.

    Dans un navigateur, ouvrez le fichier mail.php et donnez une adresse mail. Dans le terminal, vous devriez voir affiché :

    Starting fakemail
    Listening on port 10025
    Starting request
    Incoming mail
    Capturing mail to <tristan.colombo@laposte.net>
    Mail to <tristan.colombo@laposte.net> saved
    Incoming mail dispatched
    Request done

    Ceci vous indique que le mail a été correctement intercepté. Arrêtez le script par [Ctrl]+C. Vous pourrez voir le contenu de votre mail dans un fichier adresse.1 (dans le cas de notre exemple tristan.colombo@laposte.net.1)

    Passons maintenant à l’écriture du test avec PHPUnit. Nous utiliserons Selenium (voir section 6.1) pour simuler le comportement de l’utilisateur envoyant un mail et nous récupérerons ce mail par Fakemail (fichier mailtest.class.php) :

    01: <?php
    02:   require_once ‘PHPUnit/Extensions/SeleniumTestCase.php’;
    03:
    04:   class MailTest extends PHPUnit_Extensions_SeleniumTestCase
    05:   {
    06:
    07:     function setUp()
    08:     {
    09:       $this->setBrowser(‘*firefox’);
    10:       $this->setBrowserUrl(‘http://localhost/’);
    11:
    12:       $_cmd      = ‘/opt/fakemail/fakemail --path=. --host=localhost
    13:                     --port=10025 --background’;
    14:       $this->pid = `$_cmd`;
    15:       if (is_file(‘tristan.colombo@localhost.1’))
    16:       {
    17:         unlink(‘tristan.colombo@localhost.1’);
    18:       }
    19:     }
    20:
    21:     function tearDown()
    22:     {
    23:       $this->stop();
    24:
    25:       $_cmd    = ‘kill ‘ . $this->pid;
    26:       $_cmdPid = `$_cmd`;
    27:       if (is_file(‘tristan.colombo@localhost.1’))
    28:       {
    29:         unlink(‘tristan.colombo@localhost.1’);
    30:       }
    31:     }
    32:
    33:     function testMailIsSent()
    34:     {
    35:       $this->open(‘http://localhost/mail.php’);
    36:       $this->type(‘email’, ‘tristan.colombo@localhost’);
    37:       $this->click(‘send’);
    38:       $this->waitForPageToLoad(10000);
    39:
    40:       $_sent = file_get_contents(‘tristan.colombo@localhost.1’);
    41:       list($_headers, $_content) = explode("\r\n\r\n", $_sent);
    42:       $_content = trim($_content);
    43:       $this->assertEquals($_content, ‘Salut !’);
    44:     }
    45:   }
    46: ?>

    Explications sur les lignes :

    Dans les lignes 7 à 19, on définit les actions à effectuer avant les tests :

    • Lignes 9 et 10, initialisation de Selenium.
    • Lignes 12 à 14, on lance Fakemail en tâche de fond en écoute sur le port 10025 et on conserve son PID (identifiant de processus) pour pouvoir détruire le processus en fin de script.
    • Lignes 15 à 18, si le fichier tristan.colombo@localhost.1 existe déjà, on l’efface.

    Dans les lignes 21 à 31, on définit les actions à faire après avoir effectué les tests :

    • En ligne 23, on coupe la connexion à Selenium.
    • Dans les lignes 25 et 26, on arrête Fakemail en " tuant " son processus.
    • Dans les lignes 27 à 30, si le fichier tristan.colombo@localhost.1 existe, on l’efface.

    Dans les lignes 33 à 44, on effectue le test proprement dit :

    • En ligne 35, connexion à la page mail.php.
    • En ligne 36, on remplit le champ texte email avec la chaîne tristan.colombo@localhost.
    • En ligne 37, on simule l’appui sur la touche de soumission et, en ligne 38, on attend la réponse du serveur.
    • En ligne 40, on ouvre le fichier tristan.colombo@localhost.1 généré par Fakemail lors de l’interception du mail.
    • En ligne 41, on sépare l’en-tête et le corps du mail dans des variables distinctes (respectivement $_header et $_content) et, en ligne 42, on supprime les caractères invisibles de fin de chaîne (comme le saut à la ligne).
    • Enfin, en ligne 43, on s’assure que le contenu du mail correspond bien à la chaîne " Salut  ! ".

    Pour exécuter ce test, n’oubliez pas de lancer Selenium, puis :

    	phpunit MailTest mailtest.class.php

    6.3. La couverture de code

    Il est possible d’oublier de tester du code dans un projet. Pour repérer ce code qui n’est pas testé ou, en d’autres termes, pas encore couvert par un test, on utilise l’analyse de la couverture de code. Cette analyse nous fournit des informations sur les zones du code qui sont parcourues (et surtout celles qui ne le sont pas !) lorsque les tests sont exécutés.

    Pour utiliser la couverture de code, vous devez avoir installé Xdebug [11], une extension donnant de nombreuses informations de débogage. L’installation se fait grâce à PECL :

    	pecl install xdebug

    En fin d’installation, vous obtiendrez un message de la forme :

    Build process completed successfully
    Installing ‘/usr/lib/php5/20060613+lfs/xdebug.so’
    install ok: channel://pecl.php.net/xdebug-2.0.2
    configuration option "php_ini" is not set to php.ini location
    You should add "extension=xdebug.so" to php.ini

    Vous devrez alors aller dans le fichier php.ini correspondant à votre PHP CLI (voir section 2) et ajouter en fin de fichier :

    01: ; Profiling avec xdebug
    02: zend_extension = /usr/lib/php5/20060613+lfs/xdebug.so
    03: xdebug.default_enable = Off

    Le chemin vers le fichier xdebug.so donné en ligne 2 est celui que nous a renvoyé PECL dans le message de fin d’installation.

    Si tout s’est déroulé correctement, en lançant phpunit --help, vous devriez maintenant voir apparaître l’option --coverage-html.

    Nous placerons les fichiers des rapports de couverture de code dans un répertoire spécifique nommé report/ :

    	mkdir report

    Pour lancer la génération du rapport sur notre exemple, nous utiliserons la syntaxe classique en indiquant seulement où générer le rapport :

    	phpunit --coverage-html ./report CalculatorTest calculatortest.class.php

    Le rapport est présenté sous forme de fichier HTML (report/index.html) où la couverture du code est indiquée à l’aide de couleurs :

    • rouge : peu ou pas de couverture ;
    • jaune : couverture moyenne ;
    • vert : bonne couverture.

    Le rapport généré sur notre exemple est donné en figure 6.

    /img-articles/lmhs/35/cc-art-php1/fig-6.jpg

    Figure 6 : Rapport de couverture de code obtenu pour la classe CalculatorTest

    Des rapports plus détaillés sont également générés. Pour notre classe CalculatorTest, il s’agit du fichier report/calculatortest.class.php.html dont un aperçu est donné en figure 7.

     

    /img-articles/lmhs/35/cc-art-php1/fig-7.jpg
    Figure 7 : Rapport de couverture de code détaillé obtenu pour la classe CalculatorTest

    Conclusion

    Comme vous l’avez vu, l’écriture de tests avec PHPUnit est relativement simple. Une fois écrits, les tests peuvent être modifiés sans risque d’altérer le code qu’ils sont censés évaluer. Il est recommandé d’écrire ses tests avant le code : plus tôt vous rechercherez les erreurs, plus vous aurez de chances de les trouver et moins vous passerez de temps à les régler. Même si l’écriture de tests peut apparaître comme une perte de temps et de productivité, à long terme, les gains de temps se révèlent fort appréciables. De plus, lorsque vous réfléchissez à l’écriture de votre test, vous pensez également à l’écriture de votre code, ce qui devrait vous amener à plus de structuration et donc plus de logique !
    Enfin, Sebastian Bergmann, l’auteur de PHPUnit est très actif sur son projet : pas moins de 10 versions ont été publiées dans les trois derniers mois. N’oubliez pas de mettre PHPUnit à jour régulièrement !

    Liens

    • [1] http://fr.php.net/
    • [2] http://fr.php.net/manual/fr/function.assert-options.php
    • [3] http://qa.php.net/write-test.php
    • [4] http://simpletest.org/fr
    • [5] http://simpletest.cvs.sourceforge.net/simpletest/simpletest/extensions/selenium
    • [6] http://phpunit.de/
    • [7] http://fr.php.net/features.commandline
    • [8] http://www.phpunit.de/pocket_guide/3.2/fr/
    • [9] http://www.graphviz.org/
    • [10] http://www.openqa.org/selenium-rc/
    • [11] http://xdebug.org/

    Retrouvez cet article dans : Linux Magazine Hors série 35

    Posté par Tristan Colombo (tristan) | Signature : Tristan Colombo | Article paru dans Creative Commons License

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.