Tests unitaires et couverture de code
CrazyCat » 27/ 04/2010 10:07
PHP
|
Envoyer à un ami |
Fil des commentaires de ce billet
Les tests unitaires sont au développeur ce que la prose est à M. Jourdain: ils en font sans le savoir. Malheureusement, ces tests ne sont pas souvent effectués de manière complète et approfondie, et le développeur ne s'intéresse pas au complément de ces tests: la couverture de code.
Heureusement, il existe des outils qui simplifient ce travail et le rendent plus cohérent. Je traiterai ici des procédures lorsqu'on développe en PHP, en m'appuyant sur PHPUnit.
Test unitaire
Le principe du test unitaire est de vérifier le fonctionnement d'une partie de code, en testant tous les cas. Pour cela, on injecte des données connues en entrée et l'on vérifie que le résultat retourné est bien celui attendu. Il s'agit d'un fonctionnement par assertion.
Elaboration des tests
Les tests doivent permettre de vérifier tous les cas possible, c'est à dire aussi bien les résultats valides que ceux qui provoquent des erreurs. Les règles de l'art voudraient que les tests unitaires soient élaborés avant même que le squelette du code n'ait été écrit, nous allons donc procéder ainsi.
Je prendrais comme exemple une classe de calculatrice simplifiée (les 4 opérations basiques) en PHP, qui comporte bien sûr de grosses lacunes afin que nos tests soient représentatifs. Tout d'abord, nous allons écrire les tests pour chaque opération. Pour nous simplifier la tâche, nous utiliserons chaque fois le même jeu de données.
Addition
- 1, 1doit retourner 2
- 9, 3 doit retourner 12
- 7, 0 doit retourner 7
- 11, a doit retourner "erreur"
- f, 2 doit retourner "erreur"
- j, d doit retourner "erreur"
Soustraction
- 1, 1doit retourner 0
- 9, 3 doit retourner 6
- 7, 0 doit retourner 7
- 11, a doit retourner "erreur"
- f, 2 doit retourner "erreur"
- j, d doit retourner "erreur"
Multiplication
- 1, 1doit retourner 1
- 9, 3 doit retourner 27
- 7, 0 doit retourner 0
- 11, a doit retourner "erreur"
- f, 2 doit retourner "erreur"
- j, d doit retourner "erreur"
Division
- 1, 1doit retourner 1
- 9, 3 doit retourner 3
- 7, 0 doit retourner "erreur"
- 11, a doit retourner "erreur"
- f, 2 doit retourner "erreur"
- j, d doit retourner "erreur"
Le script de test
PHPUnit propose différentes assertions, nous nous bornerons à tester les égalités. Nous allons suivre l'exemple Writing tests for PHPUnit pour créer la classe de test:
<?php require_once '/var/www/math/calc.php'; // Il s'agit du fichier de notre classe "calc" require_once 'PHPUnit/Framework.php'; class calcTest extends PHPUnit_Framework_TestCase { public function testSum() { $this->assertEquals(2, calc::sum(1, 1)); $this->assertEquals(12, calc::sum(9, 3)); $this->assertEquals(7, calc::sum(7, 0)); $this->assertEquals('erreur', calc::sum(11, 'a')); $this->assertEquals('erreur', calc::sum('f', 2)); $this->assertEquals('erreur', calc::sum('j', 'd')); } public function testMinus() { $this->assertEquals(0, calc::minus(1, 1)); $this->assertEquals(6, calc::minus(9, 3)); $this->assertEquals(7, calc::minus(7, 0)); $this->assertEquals('erreur', calc::minus(11, 'a')); $this->assertEquals('erreur', calc::minus('f', 2)); $this->assertEquals('erreur', calc::minus('j', 'd')); } public function testMulti() { $this->assertEquals(1, calc::multi(1, 1)); $this->assertEquals(27, calc::multi(9, 3)); $this->assertEquals(0, calc::multi(7, 0)); $this->assertEquals('erreur', calc::multi(11, 'a')); $this->assertEquals('erreur', calc::multi('f', 2)); $this->assertEquals('erreur', calc::multi('j', 'd')); } public function testDivid() { $this->assertEquals(1, calc::divid(1, 1)); $this->assertEquals(3, calc::divid(9, 3)); $this->assertEquals('erreur', calc::divid(7, 0)); $this->assertEquals('erreur', calc::divid(11, 'a')); $this->assertEquals('erreur', calc::divid('f', 2)); $this->assertEquals('erreur', calc::divid('j', 'd')); } } ?>
La classe Calculatrice
<?php class calc { function sum($arg1, $arg2) { $res = $arg1 + $arg2; return $res; } function minus($arg1, $arg2) { $res = $arg1 - $arg2; return $res; } function multi($arg1, $arg2) { $res = $arg1 * $arg2; return $res; } function divid($arg1, $arg2) { if ($arg2 == 0) return 'erreur'; $res = $arg1 / $arg2; return $res; } } ?>
Nous pouvons maintenant lancer le test:
$ ./phpunit testCalc.php PHPUnit 3.4.9 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 4.75Mb There were 4 failures: 1) testCalc::testSum Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -erreur +11 /usr/share/PHPUnit/testCalc.php:11 2) testCalc::testMinus Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -erreur +11 /usr/share/PHPUnit/testCalc.php:20 3) testCalc::testMulti Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -erreur +0 /usr/share/PHPUnit/testCalc.php:29 4) testCalc::testDivid Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -erreur +0 /usr/share/PHPUnit/testCalc.php:39 FAILURES! Tests: 4, Assertions: 17, Failures: 4.
Le résultat parle de lui-même, toutes nos fonctions ne remplissent pas les conditions voulues, les tests ont tous échoué. Notez que PHPUnit abandonne un test sitôt qu'une assertion échoue. En effet, il n'y a eu que 17 assertions testées sur les 24.
Test
Nous allons donc modifier les fonctions pour qu'elles passent les tests et soient donc conformes à la demande:
<?php class calc { function sum($arg1, $arg2) { if (!is_numeric($arg1) || !is_numeric($arg2)) return 'erreur'; $res = $arg1 + $arg2; return $res; } function minus($arg1, $arg2) { if (!is_numeric($arg1) || !is_numeric($arg2)) return 'erreur'; $res = $arg1 - $arg2; return $res; } function multi($arg1, $arg2) { if (!is_numeric($arg1) || !is_numeric($arg2)) return 'erreur'; $res = $arg1 * $arg2; return $res; } function divid($arg1, $arg2) { if (!is_numeric($arg1) || !is_numeric($arg2)) return 'erreur'; if ($arg2 == 0) return 'erreur'; $res = $arg1 / $arg2; return $res; } } ?>
Les tests sont cette fois corrects:
PHPUnit 3.4.9 by Sebastian Bergmann. .... Time: 0 seconds, Memory: 5.50Mb OK (4 tests, 24 assertions)
Couverture de code
La couverture de code permet de vérifier que l'on passe bien dans toutes les parties du code, ce qui prouve que les tests sont complets et qu'il n'y a pas de code inutile. Dans un prochain billet, j'expliquerais comment installer PHPUnit afin qu'il puisse générer les rapports de couverture de code, mais voici le rapport correspondant aux tests que nous venons d'effectuer:
Nous pouvons voir dessus que toutes les fonctions ont été testées, nos tests sont complets. Si nous ajoutions une fonction power (pour élever à une puissance) sans ajouter de tests, voici ce que nous obtiendrions:
Cet exemple est simpliste, mais il est très parlant. La couverture de code permet de vérifier la cohérence des tests, mais aussi de votre code. Si malgré tous vos tests vous ne passez pas dans une partie de code, il y a de fortes chance qu'il soit inutile ou que vous ayez une erreur de conception.

