Prev Next

Chapitre 14. Analyse de couverture de code

 

La beauté du test ne se trouve pas dans l'effort mais dans l'efficience.

Savoir ce qui doit être testé est magnifique, et savoir ce qui est testé est magnifique.

 
  --Murali Nandigama

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. Cela aide à répondre à des questions comme :

  • Comment trouvez-vous le code qui n'est pas encore testé - ou, en d'autres mots, pas encore couvert par un test ?

  • Comment mesurez-vous la complétude du test ?

Un exemple de ce que peuvent signifier des statistiques de couverture de code est, s'il y a une méthode avec 100 lignes de code, et seulement 75 de ces lignes sont réellement exécutées quand les tests sont lancés, alors la méthode est considérée comme ayant une couverture de code de 75 pour cent.

La fonctionnalité de couverture de code de PHPUnit fait usage du composant PHP_CodeCoverage qui, à son tour, tire partie de la fonctionnalité de couverture d'instructions fournie par l'extension Xdebug de PHP.

Générons un rapport de couverture de code pour la classe CompteBancaire de Exemple 12.3, « La classe CompteBancaire complète ».

phpunit --coverage-html ./rapport CompteBancaireTest
PHPUnit 3.6.0 by Sebastian Bergmann.

...

Time: 0 seconds

OK (3 tests, 3 assertions)

Generating report, this may take a moment.

Figure 14.1, « Couverture de code pour setBalance() » montre un extrait du rapport de couverture de code. Les lignes de code qui ont été exécutés pendant le fonctionnement des tests sont surlignés en vert, les lignes de code qui sont exécutables mais n'ont pas été exécutées sont surlignées en rouge et le "code mort" est surligné en gris. Le nombre à gauche du numéro de la ligne de code indique combien de tests couvrent cette ligne.

Figure 14.1. Couverture de code pour setBalance()

Couverture de code pour setBalance()

Cliquer sur le numéro de ligne d'une ligne couverte ouvrira un panneau (voir Figure 14.2, « Panneau avec l'information des tests couvrant la ligne ») qui montre les cas de test qui couvrent cette ligne.

Figure 14.2. Panneau avec l'information des tests couvrant la ligne

Panneau avec l'information des tests couvrant la ligne

Le rapport de couverture de code de notre exemple CompteBancaire montre que nous n'avons actuellement aucun test qui appellent les méthodes setBalance(), deposerArgent() et retirerArgent() avec des valeurs acceptables. Exemple 14.1, « Test manquant pour atteindre la couverture de code complète » montre un test qui peut être ajouté à la classe de cas de test BankAccountTest pour couvrir complètement la classe CompteBancaire.

Exemple 14.1. Test manquant pour atteindre la couverture de code complète

<?php
require_once 'CompteBancaire.php';

class BankAccountTest extends PHPUnit_Framework_TestCase
{
// ...

public function testDeposerRetirerArgent()
{
$this->assertEquals(0, $this->compte_bancaire->getBalance());
$this->compte_bancaire->deposerArgent(1);
$this->assertEquals(1, $this->compte_bancaire->getBalance());
$this->compte_bancaire->retirerArgent(1);
$this->assertEquals(0, $this->compte_bancaire->getBalance());
}
}
?>

Figure 14.3, « Couverture de code pour setBalance() avec un test additionnel » montre la couverture de code de la méthode setBalance() avec le test additionnel.

Figure 14.3. Couverture de code pour setBalance() avec un test additionnel

Couverture de code pour setBalance() avec un test additionnel

Spécifier les méthodes couvertes

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 test. 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 14.2, « Tests qui indiquent quelle(s) méthode(s) ils veulent couvrir » montre un exemple.

Exemple 14.2. Tests qui indiquent quelle(s) méthode(s) ils veulent couvrir

<?php
require_once 'CompteBancaire.php';

class CompteBancaireTest extends PHPUnit_Framework_TestCase
{
protected $compte_bancaire;

protected function setUp()
{
$this->compte_bancaire = new CompteBancaire;
}

/**
* @covers CompteBancaire::getBalance
*/
public function testBalanceEstInitialementZero()
{
$this->assertEquals(0, $this->compte_bancaire->getBalance());
}

/**
* @covers CompteBancaire::retirerArgent
*/
public function testBalanceNePeutPasDevenirNegative()
{
try {
$this->compte_bancaire->retirerArgent(1);
}

catch (CompteBancaireException $e) {
$this->assertEquals(0, $this->compte_bancaire->getBalance());

return;
}

$this->fail();
}

/**
* @covers CompteBancaire::deposerArgent
*/
public function testBalanceNePeutPasDevenirNegative2()
{
try {
$this->compte_bancaire->deposerArgent(-1);
}

catch (CompteBancaireException $e) {
$this->assertEquals(0, $this->compte_bancaire->getBalance());

return;
}

$this->fail();
}

/**
* @covers CompteBancaire::getBalance
* @covers CompteBancaire::deposerArgent
* @covers CompteBancaire::retirerArgent
*/

public function testDeposerArgent()
{
$this->assertEquals(0, $this->compte_bancaire->getBalance());
$this->compte_bancaire->deposerArgent(1);
$this->assertEquals(1, $this->compte_bancaire->getBalance());
$this->compte_bancaire->retirerArgent(1);
$this->assertEquals(0, $this->compte_bancaire->getBalance());
}
}
?>

Ignorer des blocs de code

Parfois, vous avez des blocs de code que vous ne pouvez pas tester et que voulez ignorer lors de l'analyse de couverture de code. PHPUnit vous permet de faire cela en utilisant les annotations @codeCoverageIgnore, @codeCoverageIgnoreStart et @codeCoverageIgnoreEnd comme montré dans Exemple 14.3, « Utiliser les annotations @codeCoverageIgnore, @codeCoverageIgnoreStart et @codeCoverageIgnoreEnd ».

Exemple 14.3. Utiliser les annotations @codeCoverageIgnore, @codeCoverageIgnoreStart et @codeCoverageIgnoreEnd

<?php
/**
* @codeCoverageIgnore
*/
class Foo
{
public function bar()
{
}
}

class Bar
{
/**
* @codeCoverageIgnore
*/
public function foo()
{
}
}

if (FALSE) {
// @codeCoverageIgnoreStart
print '*';
// @codeCoverageIgnoreEnd
}
?>

Les lignes de code qui sont marquées comme devant être ignorées en utilisant les annotations sont comptées comme exécutées (si elles sont exécutables) et ne seront pas surlignées.

Inclure et exclure des fichiers

Par défaut, tous les fichiers de code source qui contiennent au moins une ligne de code qui a été exécutée (et seulement ces fichiers) sont inclus dans le rapport. Les fichiers de code source qui sont inclus dans le rapport peuvent être filtrés en utilisant une approche par liste noire ou liste blanche.

La liste noire est pré-remplie avec tous les fichiers de code source de PHPUnit lui-même ainsi que les tests. Quand la liste blanche est vide (par défaut), le filtrage par liste noire est utilisé. Quand la liste blanche n'est pas vide, le filtrage par liste blanche est utilisé. Quand le filtrage par liste blanche est utilisé, chaque fichier de la liste blanche est optionnellement ajouté (avec le réglage addUncoveredFilesFromWhiteList="true") au rapport de couverture de code sans se soucier qu'il ait été exécuté ou pas.

Le fichier de configuration XML de PHPUnit (voir la section intitulée « Inclure et exclure des fichiers de la couverture de code ») peut être utilisé pour contrôler les listes noires et blanches. Utiliser une liste blanche est recommandé comme meilleure pratique pour contrôler la liste des fichiers inclus dans le rapport de couverture de code.

Cas limites

Dans la plupart des cas, on peut dire sans risque que PHPUnit offre une information de couverture de code "basée sur les lignes" mais du fait de la façon dont l'information est collectée, il existe quelques cas limites qui valent la peine d'être mentionnés.

Exemple 14.4. 

<?php
// Parce qu'il s'agit d'une couverture "basée sur les lignes" et pas sur les instructions
// une ligne aura toujours un état de couverture donné
if(false) cet_appel_de_fonction_sera_compte_comme_couvert();

// Du fait de la façon dont la couverture de code fonctionne en interne, ces deux lignes sont spéciales.
/ Cette ligne sera comptée comme non exécutable
if(false)
// Cette ligne sera comptée comme couverte car c'est en fait la
// couverture de l'instruction if dans la ligne au-dessus qui
// sera montrée ici !
sera_egalement_comptee_comme_couverte();

// Pour éviter cela, il est nécessaire d'utiliser des accolades
if(false) {
cet_appel_ne_sera_jamais_compte_comme_couvert();
}
?>

Prev Next
1. Automatiser les tests
2. Objectifs de PHPUnit
3. Installer PHPUnit
4. Ecrire des tests pour PHPUnit
Dépendances des tests
Fournisseur de données
Tester des exceptions
Tester les erreurs PHP
Tester la sortie écran
Assertions
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertCount()
assertEmpty()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertInstanceOf()
assertInternalType()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertStringMatchesFormat()
assertStringMatchesFormatFile()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
5. Le lanceur de tests en ligne de commandes
Options de la ligne de commandes
6. Fixtures
Plus de setUp() que de tearDown()
Variantes
Partager les Fixtures
Etat global
7. Organiser les tests
Composer une suite de tests en utilisant le système de fichiers
Composer une suite de tests en utilisant la configuration XML
8. Tester des bases de données
Systèmes gérés pour tester des bases de données
Difficultés pour tester les bases de données
Les quatre phases d'un test de base de données
1. Nettoyer la base de données
2. Configurer les fixtures
3–5. Exécuter les tests, vérifier les résultats et nettoyer
Configuration d'un cas de test de base de données PHPUnit
Implémenter getConnection()
Implémenter getDataSet()
Qu'en est-il du schéma de base de données (DDL)?
Astuce: utilisez votre propre cas de tests abstrait de base de données
Comprendre DataSets et DataTables
Implémentations disponibles
Attention aux clefs étrangères
Implementer vos propres DataSets/DataTables
L'API de connexion
API d'assertion de base de données
Faire une assertion sur le nombre de lignes d'une table
Faire une assertion sur l'état d'une table
Faire une assertion sur le résultat d'une requête
Faire une assertion sur l'état de plusieurs tables
Foire aux questions
PHPUnit va-t'il (re-)créer le schéma de base de données pour chaque test ?
Suis-je obligé d'utiliser PDO dans mon application pour que l'extension de base de données fonctionne ?
Que puis-je faire quand j'obtiens une erreur « Too much Connections (Trop de connexions) » ?
Comment gérer les valeurs NULL avec les DataSets au format XML à plat / CSV ?
9. Tests incomplets et sautés
Tests incomplets
Sauter des tests
10. Doublure de test
Bouchons
Objets simulacres (Mock Objects)
Bouchon et simulacre pour Web Services
Simuler le système de fichiers
11. Pratiques de test
Pendant le développement
Pendant le débogage
12. Développement dirigé par les tests
Exemple du compte bancaire
13. Développement dirigé par le comportement
Exemple du jeu de Bowling
14. Analyse de couverture de code
Spécifier les méthodes couvertes
Ignorer des blocs de code
Inclure et exclure des fichiers
Cas limites
15. Autres utilisations des tests
Documentation agile
Tests transverses à l'équipe
16. Générateur de squelette
Générer un squelettre de classe de cas de test
Générer un squelette de classe à partir d'une classe de cas de test
17. PHPUnit et Selenium
Selenium Server
Installation
PHPUnit_Extensions_Selenium2TestCase
PHPUnit_Extensions_SeleniumTestCase
18. Journalisation
Résultats de test (XML)
Résultats de test (TAP)
Résultats de test (JSON)
Couverture de code (XML)
Couverture de code (TEXTE)
19. Etendre PHPUnit
Sous-classe PHPUnit_Framework_TestCase
Ecrire des assertions personnalisées
Implémenter PHPUnit_Framework_TestListener
Sous classer PHPUnit_Extensions_TestDecorator
Implémenter PHPUnit_Framework_Test
A. Assertions
B. Annotations
@assert
@author
@backupGlobals
@backupStaticAttributes
@codeCoverageIgnore*
@covers
@dataProvider
@depends
@expectedException
@expectedExceptionCode
@expectedExceptionMessage
@group
@outputBuffering
@runTestsInSeparateProcesses
@runInSeparateProcess
@test
@testdox
@ticket
C. Le fichier de configuration Configuration
PHPUnit
Série de tests
Groupes
Inclure et exclure des fichiers de la couverture de code
Journalisation
Moniteurs de tests
Configurer les réglages de PHP INI, les constantes et les variables globales
Configurer les navigateurs pour Selenium RC
D. Index
Index
E. Bibliographie
F. Copyright