En informatique, la couverture de code est une mesure utilisée pour décrire le taux de code source testé d'un programme lorsqu'il est testé par une suite de test particulière. Un programme avec un taux de couverture de code élevée a été plus complètement testé et a une plus faible chance de contenir des bugs logiciel qu'un programme avec un taux de couverture de code faible. | ||
--Wikipedia |
Dans ce chapitre, vous apprendrez tout sur la fonctionnalité de couverture de code de PHPUnit qui fournit une vision interne des parties du code de production qui sont exécutées quand les tests sont exécutés. Elle utilise le composant PHP_CodeCoverage qui tire parti de la fonctionnalité de couverture de code fournie par l'extension Xdebug de PHP.
Xdebug n'est pas distribué au sein de PHPUnit. Si une notice indiquant que l'extension Xdebug n'est pas chargé en lançant les tests, cela signifie que Xdebug n'est pas installé ou n'est pas configuré correctement. Avant de pouvoir utiliser les fonctionnalités de couverture de code dans PHPUnit, vous devez lire le guide d'installation de Xdebug..
PHPUnit peut générer un rapport de couverture de code HTML aussi bien que des fichiers de log en XML avec les informations de couverture de code dans différents formats (Clover, Crap4J, PHPUnit). Les informations de couverture de code peuvent aussi être rapporté en text (et affiché vers STDOUT) et exporté en PHP pour un traitement ultérieur.
Reportez-vous à Chapitre 3, Le lanceur de tests en ligne de commandes pour obtenir la liste des options de ligne de commande qui contrôlent la fonctionnalité de couverture de code ainsi que la section intitulée « Journalisation » pour les paramètres de configuration appropriés.
Différents indicateurs logiciels existent pour mesurer la couverture de code:
L'indicateur logiciel de couverture de ligne mesure si chaque ligne exécutable a été exécutée.
L'indicateur logiciel de couverture de fonction de méthode mesure si chaque fonction ou méthode à été invoquée. PHP_CodeCoverage considère qu'une fonction ou une méthode a été couverte seulement quand toutes ses lignes executables sont couvertes.
L'indicateur logiciel de couverture de classe et de trait mesure si chaque méthode d'une classe ou d'un trait est couverte. PHP_CodeCoverage considère qu'une classe ou un trait est couvert seulement quand toutes ses méthodes sont couvertes.
L'indicateur logiciel de couverture d'opcode mesure si chaque opcode d'une fonction ou d'une méthode a été executé lors de l'execution de la suite de test. Une ligne de code se compile habituellement en plus d'un opcode. La couverture de ligne considère une ligne de code comme couverte dès que l'un de ses opcode est executé.
L'indicateur logiciel de couverture de branche mesure
si l'expression booléenne de chaque structure de contrôle a été évalué
à true
et à false
pendant l'execution
de la suite de test.
L'indicateur logiciel de couverture de chemin mesure si chacun des chemins d'execution possible dans une fonction ou une méthode ont été suivit lors de l'execution de la suite de test. Un chemin d'execution est une séquence unique de branches depuis l'entrée de la fonction ou de la méthode jusqu'à sa sortie.
L'index Change Risk Anti-Patterns (CRAP) est calculé en se basant sur la complexité cyclomatique et la couverture de code d'une portion de code. Du code qui n'est pas trop complexe et qui a une couverture de code adéquate aurat un index CRAP faible. L'index CRAP peut être baissé en écrivant des tests et en refactorisant le code pour diminuer sa complexité.
L'indicateur logiciel de Couverture d'Opcode, Couverture de branche, et de Couverture de chemin ne sont pas encore supportés par PHP_CodeCoverage.
Il est requis de configurer une liste blanche pour dire à
PHPUnit quels fichiers de code source inclure dans le raport de couverture de code.
Cela peut être fait en utilisant l'option de ligne de commande --whitelist
ou via le fichier de configuration (voir la section intitulée « Inclure des fichiers de la couverture de code »).
Optionnellement, tous les fichiers en liste blanche peut être ajouté au rapport
de couverture de code en paramétrant addUncoveredFilesFromWhitelist="true"
dans votre fichier de configuration PHPUnit (voir la section intitulée « Inclure des fichiers de la couverture de code »). Cela autorise
l'inclusion de fichiers quine sont pas encore testé du tout. Si vous voulez avoir des
information sur quelles lignes d'un fichier non couvert sont exécutable,
par exemple, vous devez également définir
processUncoveredFilesFromWhitelist="true"
dans votre
fichier de configuration PHPUnit (voir la section intitulée « Inclure des fichiers de la couverture de code »).
Notez que le chargement des fichiers de code source qui est effectué lorsque
processUncoveredFilesFromWhitelist="true"
est défini peut
causer des problèmes quand un fichier de code source contient du code en dehors de la portée
d'une classe ou d'une fonction, par exemple.
Parfois, vous avez des blocs de code que vous ne pouvez pas tester et que vous pouvez vouloir
ignorer lors de l'analyse de la couverture de code. PHPUnit vous permet de le faire
en utilisant les annotations @codeCoverageIgnore
,
@codeCoverageIgnoreStart
et
@codeCoverageIgnoreEnd
comme montré dans
Exemple 11.1, « Utiliser les annotations @codeCoverageIgnore
, @codeCoverageIgnoreStart
et @codeCoverageIgnoreEnd
».
Exemple 11.1. Utiliser les annotations @codeCoverageIgnore
, @codeCoverageIgnoreStart
et @codeCoverageIgnoreEnd
<?php use PHPUnit\Framework\TestCase; /** * @codeCoverageIgnore */ class Foo { public function bar() { } } class Bar { /** * @codeCoverageIgnore */ public function foo() { } } if (false) { // @codeCoverageIgnoreStart print '*'; // @codeCoverageIgnoreEnd } exit; // @codeCoverageIgnore ?>
Les lignes de code ignorées (marquées comme ignorées à l'aide des annotations) sont comptées comme exécutées (si elles sont exécutables) et ne seront pas mises en évidence.
L'annotation @covers
(voir
Tableau B.1, « Annotations pour indiquer quelles méthodes sont couvertes par un test ») peut être
utilisée dans le code de test pour indiquer quelle(s) méthode(s) une méthode de test
veut tester. Si elle est fournie, seules les informations de couverture de code pour
la(les) méthode(s) indiquées seront prises en considération.
Exemple 11.2, « Tests qui indiquent quelle(s) méthode(s) ils veulent couvrir »
montre un exemple.
Exemple 11.2. Tests qui indiquent quelle(s) méthode(s) ils veulent couvrir
<?php use PHPUnit\Framework\TestCase; class BankAccountTest extends TestCase { protected $ba; protected function setUp() { $this->ba = new BankAccount; } /** * @covers BankAccount::getBalance */ public function testBalanceIsInitiallyZero() { $this->assertEquals(0, $this->ba->getBalance()); } /** * @covers BankAccount::withdrawMoney */ public function testBalanceCannotBecomeNegative() { try { $this->ba->withdrawMoney(1); } catch (BankAccountException $e) { $this->assertEquals(0, $this->ba->getBalance()); return; } $this->fail(); } /** * @covers BankAccount::depositMoney */ public function testBalanceCannotBecomeNegative2() { try { $this->ba->depositMoney(-1); } catch (BankAccountException $e) { $this->assertEquals(0, $this->ba->getBalance()); return; } $this->fail(); } /** * @covers BankAccount::getBalance * @covers BankAccount::depositMoney * @covers BankAccount::withdrawMoney */ public function testDepositWithdrawMoney() { $this->assertEquals(0, $this->ba->getBalance()); $this->ba->depositMoney(1); $this->assertEquals(1, $this->ba->getBalance()); $this->ba->withdrawMoney(1); $this->assertEquals(0, $this->ba->getBalance()); } } ?>
Il est également possible d'indiquer qu'un test ne doit couvrir
aucune méthode en utilisant l'annotation
@coversNothing
(voir
la section intitulée « @coversNothing »). Ceci peut être
utile quand on écrit des tests d'intégration pour s'assurer que vous
ne générez une couverture de code avec des tests unitaires.
Exemple 11.3. Un test qui indique qu'aucune méthode ne doit être couverte
<?php use PHPUnit\Framework\TestCase; class GuestbookIntegrationTest extends PHPUnit_Extensions_Database_TestCase { /** * @coversNothing */ public function testAddEntry() { $guestbook = new Guestbook(); $guestbook->addEntry("suzy", "Hello world!"); $queryTable = $this->getConnection()->createQueryTable( 'guestbook', 'SELECT * FROM guestbook' ); $expectedTable = $this->createFlatXmlDataSet("expectedBook.xml") ->getTable("guestbook"); $this->assertTablesEqual($expectedTable, $queryTable); } } ?>
Cette section présente des cas limites remarquables qui conduisent à des informations de couverture de code prêtant à confusion.
Exemple 11.4.
<?php use PHPUnit\Framework\TestCase; // Because it is "line based" and not statement base coverage // one line will always have one coverage status if (false) this_function_call_shows_up_as_covered(); // Due to how code coverage works internally these two lines are special. // This line will show up as non executable if (false) // This line will show up as covered because it is actually the // coverage of the if statement in the line above that gets shown here! will_also_show_up_as_covered(); // To avoid this it is necessary that braces are used if (false) { this_call_will_never_show_up_as_covered(); } ?>