L'une des parties les plus consommatrices en temps lors de l'écriture de tests est d'écrire le code pour configurer le monde dans un état connu puis de le remettre dans son état initial quand le test est terminé. Cet état connu est appelé la fixture du test.
Dans Exemple 2.1, « Tester des opérations de tableau avec PHPUnit », la
fixture était simplement le tableau sauvegardé dans la variable $fixture
.
La plupart du temps, cependant, la fixture sera beaucoup plus complexe
qu'un simple tableau, et le volume de code nécessaire pour la mettre en place
croîtra dans les mêmes proportions. Le contenu effectif du test sera perdu
dans le bruit de configuration de la fixture. Ce problème s'aggrave quand
vous écrivez plusieurs tests doté de fixtures similaires. Sans l'aide du
framework de test, nous aurions à dupliquer le code qui configure la fixture
pour chaque test que nous écrivons.
PHPUnit gère le partage du code de configuration. Avant qu'une méthode de test ne soit
lancée, une méthode canevas appelée setUp()
est invoquée.
setUp()
est l'endroit où vous créez les objets sur lesquels
vous allez passer les tests. Une fois que la méthode de test est finie, qu'elle ait
réussi ou échoué, une autre méthode canevas appelée
tearDown()
est invoquée. tearDown()
est l'endroit où vous nettoyez les objets sur lesquels vous avez passé les tests.
Dans Exemple 2.2, « Utiliser l'annotation @depends
pour exprimer des dépendances » nous avons
utilisé la relation producteur-consommateur entre les tests pour partager une fixture. Ce
n'est pas toujours souhaitable ni même possible. Exemple 4.1, « Using setUp() to create the stack fixture »
montre comme nous pouvons écrire les tests de PileTest
de telle façon
que ce n'est pas la fixture elle-même qui est réutilisée mais le code qui l'a créée.
D'abord nous déclarons la variable d'instance, $pile
, que nous
allons utiliser à la place d'une variable locale à la méthode. Puis nous plaçons
la création de la fixture tableau
dans la méthode
setUp()
. Enfin, nous supprimons le code redondant des méthodes
de test et nous utilisons la variable d'instance nouvellement introduite.
$this->pile
, à la place de la variable locale à la méthode
$pile
avec la méthode d'assertion assertEquals()
.
Exemple 4.1. Using setUp() to create the stack fixture
<?php class PileTest extends PHPUnit_Framework_TestCase { protected $pile; protected function setUp() { $this->pile = array(); } public function testVide() { $this->assertTrue(empty($this->pile)); } public function testPush() { array_push($this->pile, 'foo'); $this->assertEquals('foo', $this->pile[count($this->pile)-1]); $this->assertFalse(empty($this->pile)); } public function testPop() { array_push($this->pile, 'foo'); $this->assertEquals('foo', array_pop($this->pile)); $this->assertTrue(empty($this->pile)); } } ?>
Les méthodes canevas setUp()
et tearDown()
sont exécutées une fois pour chaque méthode de test (et pour les nouvelles instances)
de la classe de cas de test.
De plus, les méthodes canevas setUpBeforeClass()
et
tearDownAfterClass()
sont appelées respectivement avant
que le premier test de la classe de cas de test ne soit exécuté et après
que le dernier test de la classe de test a été exécuté.
L'exemple ci-dessous montre toutes les méthodes canevas qui sont disponibles dans une classe de cas de test.
Exemple 4.2. Exemple montrant toutes les méthodes canevas disponibles
<?php class TemplateMethodsTest extends PHPUnit_Framework_TestCase { public static function setUpBeforeClass() { fwrite(STDOUT, __METHOD__ . "\n"); } protected function setUp() { fwrite(STDOUT, __METHOD__ . "\n"); } protected function assertPreConditions() { fwrite(STDOUT, __METHOD__ . "\n"); } public function testOne() { fwrite(STDOUT, __METHOD__ . "\n"); $this->assertTrue(TRUE); } public function testTwo() { fwrite(STDOUT, __METHOD__ . "\n"); $this->assertTrue(FALSE); } protected function assertPostConditions() { fwrite(STDOUT, __METHOD__ . "\n"); } protected function tearDown() { fwrite(STDOUT, __METHOD__ . "\n"); } public static function tearDownAfterClass() { fwrite(STDOUT, __METHOD__ . "\n"); } protected function onNotSuccessfulTest(Exception $e) { fwrite(STDOUT, __METHOD__ . "\n"); throw $e; } } ?>
phpunit TemplateMethodsTest
PHPUnit 4.2.0 by Sebastian Bergmann.
TemplateMethodsTest::setUpBeforeClass
TemplateMethodsTest::setUp
TemplateMethodsTest::assertPreConditions
TemplateMethodsTest::testOne
TemplateMethodsTest::assertPostConditions
TemplateMethodsTest::tearDown
.TemplateMethodsTest::setUp
TemplateMethodsTest::assertPreConditions
TemplateMethodsTest::testTwo
TemplateMethodsTest::tearDown
TemplateMethodsTest::onNotSuccessfulTest
FTemplateMethodsTest::tearDownAfterClass
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) TemplateMethodsTest::testTwo
Failed asserting that <boolean:false> is true.
/home/sb/TemplateMethodsTest.php:30
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
setUp()
et tearDown()
sont sympathiquement
symétriques en théorie mais pas en pratique. En pratique, vous n'avez besoin
d'implémenter tearDown()
que si vous avez alloué
des ressources externes telles que des fichiers ou des sockets dans
setUp()
. Si votre setUp()
ne crée simplement
que de purs objets PHP, vous pouvez généralement ignorer tearDown()
.
Cependant, si vous créez de nombreux objets dans votre setUp()
, vous
pourriez vouloir libérer (unset()
) les variables pointant vers
ces objets dans votre tearDown()
de façon à ce qu'ils puissent être
récupérés par le ramasse-miettes. Le nettoyage des objets de cas de test n'est pas
prévisible.
Que se passe-t'il si vous avez deux tests avec deux setups légèrement différents ? Il y a deux possibilités :
Si le code des setUp()
ne diffère que légèrement, extrayez le
code qui diffère du code de setUp()
pour le mettre dans la méthode
de test.
Si vous avez vraiment deux setUp()
différentes, vous
avez besoin de classes de cas de test différentes. Nommez les classes selon
les différences constatées dans les setup.
Il existe quelques bonnes raisons pour partager des fixtures entre les tests, mais dans la plupart des cas la nécessité de partager une fixture entre plusieurs tests résulte d'un problème de conception non résolu.
Un bon exemple de fixture qu'il est raisonnable de partager entre plusieurs tests est une connexion à une base de données : vous vous connectez une fois à la base de données et vous réutilisez cette connexion au lieu d'en créer une nouvelle pour chaque test. Ceci rend vos tests plus rapides.
Exemple 4.3, « Partager les fixtures entre les tests d'une série de tests »
utilise les méthodes canevas setUpBeforeClass()
et
tearDownAfterClass()
pour respectivement établir la connexion à la
base de données avant le premier test de la classe de cas de test et pour
de déconnecter de la base de données après le dernier test du cas de test.
Exemple 4.3. Partager les fixtures entre les tests d'une série de tests
<?php class DatabaseTest extends PHPUnit_Framework_TestCase { protected static $dbh; public static function setUpBeforeClass() { self::$dbh = new PDO('sqlite::memory:'); } public static function tearDownAfterClass() { self::$dbh = NULL; } } ?>
On n'insistera jamais assez sur le fait que partager les fixtures entre les tests réduit la valeur de ces tests. Le problème de conception sous-jacent est que les objets ne sont pas faiblement couplés. Vous pourrez obtenir de meilleurs résultats en résolvant le problème de conception sous-jacent puis en écrivant des tests utilisant des bouchons (voir Chapitre 8, Doublure de test), plutôt qu'en créant des dépendances entre les tests à l'exécution et en ignorant l'opportunité d'améliorer votre conception.
Il est difficile de tester du code qui utilise des singletons. La même chose est vraie pour le code qui utilise des variables globales. Typiquement, le code que vous voulez tester est fortement couplé avec une variable globale et vous ne pouvez pas contrôler sa création. Un problème additionnel réside dans le fait qu'un test qui modifie une variable globale peut faire échouer un autre test.
En PHP, les variables globales fonctionnent comme ceci :
Une variable globale $foo = 'bar';
est enregistrée comme $GLOBALS['foo'] = 'bar';
.
La variable $GLOBALS
est une variable appelée super-globale.
Les variables super-globales sont des variables internes qui sont toujours disponibles dans toutes les portées.
Dans la portée d'une fonction ou d'une méthode, vous pouvez accéder à la variable globale $foo
soit en accédant directement à $GLOBALS['foo']
soit en utilisant global $foo;
pour créer une variable locale faisant référence à la variable globale.
A part les variables globales, les attributs statiques des classes font également partie de l'état global.
Par défaut, PHPUnit exécute vos tests de façon à ce que des modifications
aux variables globales et super-globales (
$GLOBALS
,
$_ENV
, $_POST
,
$_GET
, $_COOKIE
,
$_SERVER
, $_FILES
,
$_REQUEST
) n'affectent pas les autres tests. Optionnellement,
cette indépendance peut être étendue aux attributs statiques des classes.
L'implémentation des opérations de sauvegarde et de restauration des attributs statiques des classes nécessite PHP 5.3 (ou supérieur).
L'implémentation des opérations de sauvegarde et de restauration des variables
globales et des attributs statiques des classes utilise
serialize()
et unserialize()
.
Les objets de certaines classes qui sont fournis pas PHP lui-même, tel que
PDO
par exemple, ne peuvent pas être sérialisés si bien que
l'opération de sauvegarde va échouer quand un tel objet sera enregistré dans le tableau
$GLOBALS
, par exemple.
L'annotation @backupGlobals
qui est discutée dans
la section intitulée « @backupGlobals » peut être utilisée pour
contrôler les opérations de sauvegarde et de restauration des variables globales.
Alternativement, vous pouvez fournir une liste noire des variables globales qui doivent
être exclues des opérations de sauvegarde et de restauration comme ceci :
class MonTest extends PHPUnit_Framework_TestCase { protected $backupGlobalsBlacklist = array('globalVariable'); // ... }
Merci de noter que le réglage de l'attribut $backupGlobalsBlacklist
à l'intérieur de la méthode setUp()
, par exemple, n'a aucun effet.
L'annotation @backupStaticAttributes
qui est discutée dans
la section intitulée « @backupStaticAttributes » peut être utilisée pour
contrôler les opérations de sauvegarde et de restauration des attributs statiques.
Alternativement, vous pouvez fournir une liste noire des attributs statiques qui doivent
être exclus des opérations de sauvegarde et de restauration comme ceci :
class MonTest extends PHPUnit_Framework_TestCase { protected $backupStaticAttributesBlacklist = array( 'className' => array('attributeName') ); // ... }
Merci de noter que le réglage de l'attribut $backupStaticAttributesBlacklist
à l'intérieur de la méthode setUp()
, par exemple, n'a aucun effet.