Blog // Exirel.me

À la découverte de PHPUnit

Par Florian Strzelecki - 19:40 - 10.05.2011

Tags : Programmation, Bonne pratique, PHP, Unit Testing, Technique

Les tests unitaires, je connais depuis que je fais du python, et je sais qu'il est possible d'en faire avec php, mais je n'avais jamais vraiment essayer avec ce langage. Il faut dire que mes dernières expériences professionnelles n'étaient pas vraiment portées sur la qualité du code (ce qui est dommage, et particulièrement frustrant pour moi).

Mais ce matin, j'ai relevé ma motivation pour m'intéresser de plus près aux tests unitaires avec php. Je suis tombé sur PHPUnit, et j'ai donc passé la matinée à l'installer, l'utiliser, et à me poser des questions.

Cet article traite donc de mon exploration (très récente) de PHPUnit, de sa phase d'installation puis d'utilisation. Le contexte est un petit projet de carte en 2D gérée par Tile (2D-tile based system) avec un algorithme de shadowcasting recursif et tout un tas d'autres détails.

D'ailleurs, ce petit projet sera l'occasion d'un autre article après la publication d'une première version stable (probablement sur bitbucket) avec sa documentation. Oui, c'est du teasing, c'est mal, mais je parle d'abords des tests unitaires. Si je veux.

Phase d'installation

N'ayant pas beaucoup travaillé avec php sur ma machine actuelle (une installation récente sur laquelle je n'ai fait que du python/django), j'ai dû commencer par installer PEAR, qui présente l'avantage de la simplicité pour installer PHPUnit.

Accessoirement, c'est aussi pratique pour installer PHPDocumentor que j'ai déjà l'habitude d'utiliser pour générer une documentation à partir du code source.

Sur mon Ubuntu, voici ce que cela donne :

fstrzelecki@Briareos:~$ sudo aptitude install php-pear
fstrzelecki@Briareos:~$ sudo pear upgrade PEAR

La première ligne, c'est pour installer PEAR, et la seconde, c'est pour mettre PEAR à jour. Il se trouve que l'installation de PHPUnit requiert des librairies qui requièrent elles-mêmes une version à jour de PEAR.

Je sais, ça a l'air un peu stupide d'utiliser PEAR au lieu d'Aptitude pour mettre à jour PEAR, mais la version de PEAR installée via les dépôts de mon Ubuntu n'est pas la bonne.

Détail : pour avoir des informations sur les dépendances de librairies et d'extensions php, il suffit d'utiliser pear info phpunit/PHPUnit. J'en ai d'ailleurs profité pour installer quelques extensions php très pratique (dont php-curl).

Librairies, chemins et commandes

Une fois cette installation faites, j'ai accès au répertoire /usr/share/php/PHPUnit qui contient les fichiers de PHPUnit d'une part, et à la commande /usr/bin/phpunit (que je peux donc appeler directement avec phpunit) d'autre part.

C'est cette commande qui va nous être utile dès maintenant, pour la phase d'utilisation.

Phase d'utilisation.

Après m'être sorti de l'installation (j'ai perdu un peu de temps sur la mise à jour de PEAR à laquelle je n'avais pas pensé au début) j'ai attaqué la ligne de commande.

Le mieux, c'est encore d'utiliser l'option --help de la commande phpunit, pour obtenir la liste des commandes disponibles.

Mon but à l'origine était de faire des tests sur une classe, j'ai donc cherché à générer un squelette de procédures de tests pour ladite classe. Pour cela, je vous conseille l'option --skeleton-test en lui fournissant, en paramètre, le nom de la classe à tester, et le fichier dans lequel se trouve la classe.

Voici ce que j'ai fait :

fstrzelecki@Briareos:~/Workspace/tapcaz/src/lib$ phpunit --skeleton-test Map map.php
 PHPUnit 3.5.13 by Sebastian Bergmann.

Wrote skeleton for "MapTest" to "/home/fstrzelecki/Workspace/tapcaz/src/lib/MapTest.php".

Vous pouvez constater qu'il a générer le fichier MapTest.php dans le même répertoire que le fichier où se trouve le fichier de la classe (j'ai testé en me mettant ailleurs, c'est le même comportement).

J'avoue que jusqu'à présent je n'ai pas encore trouver comment modifier ce comportement. Je me contente de déplacer les fichiers générés là où je souhaite les avoir, cela n'a aucun impact sur le fonctionnement de PHPUnit derrière.

Fichiers de test générés

Le fichier MapTest.php contient une classe (MapTest) qui étend la classe PHPUnit_Framework_TestCase. C'est le plus important.

Ensuite, deux méthodes importantes ont été ajoutées :

Ces deux méthodes doivent être modifiées pour effectuer tous les traitements (créer et supprimer une base de test, initialiser des instances, vérifier des configurations, etc.) qui doivent être fait avant et après le lancement des tests.

Ensuite, il m'a aussi créé un tas de méthodes publiques commençant par "test" avec le nom de mes fonctions derrières. C'est gentil, ça m'aide vaguement à voir ce que je dois écrire.

Phase de développement des tests

Les méthodes de notre classe de test qui doivent être utilisées par la commande phpunit suivent toutes la même nomenclature : test*. Ensuite, il suffit d'utiliser les méthodes de tests (les Assertions) fournis par PHPUnit dans vos méthodes de test.

C'est simple et efficace.

Assertions

Pour effectuer un test, il faut utiliser les Assertions proposées par PHPUnit, à l'intérieur des méthodes de la classe de tests.

Exemple :

public function testGetTileInstance()
{
    $tile = $this -> object -> get_tile(0, 0);
    return $this -> assertInstanceOf('Tile', $tile);
}

Je vous laisse regarder la liste des Assertions, il y a de quoi faire ! Et chacune est détaillée dans la documentation.

Exceptions

Cependant, vous pouvez légitimement vous demander "Et comment gérer les exceptions ?". Je me suis posé la question, et voici donc un exemple de solution :

    /**
     * @expectedException TileDoesNotExistException
     */
    public function testGetTileException_X_Under()
    {
        $tile = $this -> object -> get_tile(-1, 0);
    }

En utilisant un tag de documentation particulier qui va être analysé par PHPUnit, j'indique à ce dernier qu'il doit s'attendre à une exception de la classe TileDoesNotExistException au passage du test. Il existe une autre façon de faire, en utilisant une méthode à l'intérieur de votre méthode de test (voir la documentation).

Remarque : lorsque j'ai écrit cette méthode de test, je levais une exception de la classe "Exception". J'ai volontairement écrit "TileDoesNotExistException", pour me rappeler de coder ladite exception dans ma librairie : je dirige mon développement en commençant par les tests !

Dépendances de tests

Si un test nécessite la réussite d'un autre pour être effectué, il faut pouvoir l'indiquer à PHPUnit. Pour cela, j'ai utilisé la méthode du tag de documentation.

Exemple :

/**
 * @depends testGetTileInstance
 * @depends testGetTileTypeWall
 */
public function testSetTile()
{
    // some assert
}

La encore, j'ai simplement suivi la documentation sur les dépendances de tests.

Phase finale : lancer les tests

Une fois que j'ai fait/écrit tout ça, j'ai cessé de faire de simple "test" de la ligne de commande, pour lancer "pour de vrai" les tests unitaires de ma librairie.

Considérons ma classe de test MapTest mon fichier de test "MapTest.php", voici la ligne de commande que j'ai utilisée, et son retour :

fstrzelecki@Briareos:~/Workspace/tapcaz/src/test$ phpunit MapTest
PHPUnit 3.5.13 by Sebastian Bergmann.

............

Time: 0 seconds, Memory: 5.50Mb

OK (12 tests, 46 assertions)

Et c'est tout tout. J'ai trouvé cela un peu "léger", mais j'ai pu voir qu'il y avait des options diverses et variées...

Faites des tests

Et voilà, vous avez un apperçu rapide de PHPUnit, un apperçu qui, je le pense, est suffisant pour démarrer des tests unitaires.

Je suppose que je vais passer quelques temps encore à tester ce que cela peut donner, pour avoir un résultat optimum. Peut-être vais-je écrire encore des choses là dessus.

En tout cas, je ne peux que conseiller d'utiliser PHPUnit pour vos tests unitaires, même (et surtout) sur de petites librairies comme je le fais en ce moment.

Vous êtes libre de vous organiser comme vous voulez pour les tests, PHPUnit n'est pas trop pénible, et c'est déjà quelque chose d'écrire des tests : c'est surtout une habitude à prendre, une philosophie, et pas seulement un outil.

My 2 cents sur PHPUnit.