phpunit-book, test unitaires en php


phpunit-book, test unitaires en php

 

Manuel PHPUnit Sebastian Bergmann Manuel PHPUnit Sebastian Bergmann Date de publication Edition pour PHPUnit 3.7 mise à jour le 2013-02-22. Copyright © 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Sebastian Bergmann Cette oeuvre est soumise à la license Creative Commons Attribution 3.0 non transposée. iii Table des matières 1. Automatiser les tests ................................................................................................... 1 2. Objectifs de PHPUnit ................................................................................................. 3 3. Installer PHPUnit ....................................................................................................... 5 4. Ecrire des tests pour PHPUnit ...................................................................................... 7 Dépendances des tests ............................................................................................ 7 Fournisseur de données .......................................................................................... 9 Tester des exceptions ............................................................................................ 12 Tester les erreurs PHP .......................................................................................... 16 Tester la sortie écran ............................................................................................ 18 Assertions ........................................................................................................... 19 assertArrayHasKey() ........................................................................... 19 assertClassHasAttribute() ............................................................... 20 assertClassHasStaticAttribute() ................................................... 21 assertContains() ................................................................................. 22 assertContainsOnly() ......................................................................... 24 assertCount() ....................................................................................... 25 assertEmpty() ....................................................................................... 26 assertEqualXMLStructure() ............................................................... 27 assertEquals() ..................................................................................... 29 assertFalse() ....................................................................................... 35 assertFileEquals() ............................................................................. 36 assertFileExists() ............................................................................. 37 assertGreaterThan() ........................................................................... 38 assertGreaterThanOrEqual() ............................................................. 39 assertInstanceOf() ............................................................................. 40 assertInternalType() ......................................................................... 41 assertJsonFileEqualsJsonFile() ..................................................... 42 assertJsonStringEqualsJsonFile() ................................................. 42 assertJsonStringEqualsJsonString() ............................................. 43 assertLessThan() ................................................................................. 44 assertLessThanOrEqual() ................................................................... 45 assertNull() ......................................................................................... 46 assertObjectHasAttribute() ............................................................. 47 assertRegExp() ..................................................................................... 48 assertStringMatchesFormat() ........................................................... 49 assertStringMatchesFormatFile() ................................................... 50 assertSame() ......................................................................................... 51 assertSelectCount() ........................................................................... 53 assertSelectEquals() ......................................................................... 55 assertSelectRegExp() ......................................................................... 56 assertStringEndsWith() ..................................................................... 58 assertStringEqualsFile() ................................................................. 59 assertStringStartsWith() ................................................................. 60 assertTag() ........................................................................................... 61 assertThat() ......................................................................................... 63 assertTrue() ......................................................................................... 65 assertXmlFileEqualsXmlFile() ......................................................... 66 assertXmlStringEqualsXmlFile() ..................................................... 67 assertXmlStringEqualsXmlString() ................................................. 68 5. Le lanceur de tests en ligne de commandes ................................................................... 70 Options de la ligne de commandes .......................................................................... 70 6. Fixtures .................................................................................................................. 75 Plus de setUp() que de tearDown() ......................................................................... 77 Variantes ............................................................................................................ 78 Partager les Fixtures ............................................................................................. 78 Manuel PHPUnit iv Etat global .......................................................................................................... 78 7. Organiser les tests .................................................................................................... 81 Composer une suite de tests en utilisant le système de fichiers ..................................... 81 Composer une suite de tests en utilisant la configuration XML ..................................... 82 8. Tester des bases de données ....................................................................................... 84 Systèmes gérés pour tester des bases de données ....................................................... 84 Difficultés pour tester les bases de données .............................................................. 84 Les quatre phases d'un test de base de données ......................................................... 85 1. Nettoyer la base de données ....................................................................... 85 2. Configurer les fixtures ............................................................................... 85 3?5. Exécuter les tests, vérifier les résultats et nettoyer ....................................... 86 Configuration d'un cas de test de base de données PHPUnit ......................................... 86 Implémenter getConnection() ................................................................. 86 Implémenter getDataSet() ....................................................................... 87 Qu'en est-il du schéma de base de données (DDL)? ........................................... 87 Astuce: utilisez votre propre cas de tests abstrait de base de données ..................... 87 Comprendre DataSets et DataTables ........................................................................ 89 Implémentations disponibles .......................................................................... 89 Attention aux clefs étrangères ........................................................................ 97 Implementer vos propres DataSets/DataTables .................................................. 97 L'API de connexion .............................................................................................. 98 API d'assertion de base de données ......................................................................... 99 Faire une assertion sur le nombre de lignes d'une table ....................................... 99 Faire une assertion sur l'état d'une table ........................................................... 99 Faire une assertion sur le résultat d'une requête ................................................ 100 Faire une assertion sur l'état de plusieurs tables ............................................... 101 Foire aux questions ............................................................................................ 101 PHPUnit va-t'il (re-)créer le schéma de base de données pour chaque test ? ........... 101 Suis-je obligé d'utiliser PDO dans mon application pour que l'extension de base de données fonctionne ? ................................................................................... 101 Que puis-je faire quand j'obtiens une erreur « Too much Connections (Trop de connexions) » ? .......................................................................................... 102 Comment gérer les valeurs NULL avec les DataSets au format XML à plat / CSV ? ...................................................................................................... 102 9. Tests incomplets et sautés ........................................................................................ 103 Tests incomplets ................................................................................................ 103 Sauter des tests .................................................................................................. 104 Sauter des tests en utilisant requires ................................................................... 105 10. Doublure de test ................................................................................................... 107 Bouchons .......................................................................................................... 107 Objets simulacres (Mock Objects) ......................................................................... 113 Bouchon et simulacre pour Web Services ............................................................... 116 Simuler le système de fichiers .............................................................................. 117 11. Pratiques de test ................................................................................................... 120 Pendant le développement ................................................................................... 120 Pendant le débogage ........................................................................................... 120 12. Développement dirigé par les tests ........................................................................... 122 Exemple du compte bancaire ................................................................................ 122 13. Développement dirigé par le comportement ............................................................... 127 Exemple du jeu de Bowling ................................................................................. 128 14. Analyse de couverture de code ................................................................................ 133 Spécifier les méthodes couvertes ........................................................................... 135 Ignorer des blocs de code .................................................................................... 137 Inclure et exclure des fichiers ............................................................................... 138 Cas limites ........................................................................................................ 138 15. Autres utilisations des tests ..................................................................................... 139 Documentation agile ........................................................................................... 139 Tests transverses à l'équipe .................................................................................. 140 Manuel PHPUnit v 16. Générateur de squelette .......................................................................................... 141 Générer un squelettre de classe de cas de test .......................................................... 141 Générer un squelette de classe à partir d'une classe de cas de test ................................ 143 17. PHPUnit et Selenium ............................................................................................. 146 Selenium Server ................................................................................................. 146 Installation ........................................................................................................ 146 PHPUnit_Extensions_Selenium2TestCase ............................................................... 146 PHPUnit_Extensions_SeleniumTestCase ................................................................ 148 18. Journalisation ....................................................................................................... 154 Résultats de test (XML) ...................................................................................... 154 Résultats de test (TAP) ....................................................................................... 155 Résultats de test (JSON) ...................................................................................... 155 Couverture de code (XML) .................................................................................. 156 Couverture de code (TEXTE) ............................................................................... 156 19. Etendre PHPUnit .................................................................................................. 158 Sous-classe PHPUnit_Framework_TestCase ............................................................ 158 Ecrire des assertions personnalisées ....................................................................... 158 Implémenter PHPUnit_Framework_TestListener ...................................................... 159 Sous classer PHPUnit_Extensions_TestDecorator ..................................................... 160 Implémenter PHPUnit_Framework_Test ................................................................. 161 A. Assertions ............................................................................................................. 163 B. Annotations ........................................................................................................... 167 author ......................................................................................................... 167 backupGlobals ........................................................................................... 167 backupStaticAttributes ......................................................................... 167 codeCoverageIgnore* ............................................................................... 168 covers ......................................................................................................... 168 coversNothing ........................................................................................... 169 dataProvider ............................................................................................. 169 depends ....................................................................................................... 169 expectedException ................................................................................... 170 expectedExceptionCode ........................................................................... 170 expectedExceptionMessage ..................................................................... 170 group ........................................................................................................... 171 outputBuffering ....................................................................................... 172 requires ..................................................................................................... 172 runTestsInSeparateProcesses ............................................................... 172 runInSeparateProcess ............................................................................. 172 test ............................................................................................................. 172 testdox ....................................................................................................... 173 ticket ......................................................................................................... 173 C. Le fichier de configuration Configuration ................................................................... 174 PHPUnit ........................................................................................................... 174 Série de tests ..................................................................................................... 174 Groupes ............................................................................................................ 175 Inclure et exclure des fichiers de la couverture de code ............................................. 175 Journalisation ..................................................................................................... 176 Moniteurs de tests .............................................................................................. 177 Configurer les réglages de PHP INI, les constantes et les variables globales .................. 177 Configurer les navigateurs pour Selenium RC ......................................................... 178 D. Index ................................................................................................................... 179 E. Bibliographie ......................................................................................................... 185 F. Copyright .............................................................................................................. 186 vi Liste des illustrations 14.1. Couverture de code pour setBalance() ............................................................. 134 14.2. Panneau avec l'information des tests couvrant la ligne ............................................... 134 14.3. Couverture de code pour setBalance() avec un test additionnel ............................. 135 18.1. Sortie de couverture de code en couleurs sur la ligne de commandes ............................ 157 vii Liste des tableaux 4.1. Méthodes pour tester des exceptions ......................................................................... 16 4.2. Méthodes pour tester les sorties écran ....................................................................... 19 4.3. Contraintes ........................................................................................................... 64 9.1. API pour les tests incomplets ................................................................................. 104 9.2. API pour sauter des tests ....................................................................................... 105 9.3. Usages possibles de requires ............................................................................... 105 10.1. Matchers ........................................................................................................... 115 16.1. Variantes gérées par l'annotation assert ................................................................ 143 17.1. API de Selenium Server: configuration ................................................................... 149 17.2. Assertions ......................................................................................................... 151 17.3. Méthodes canevas ............................................................................................... 152 A.1. Assertions .......................................................................................................... 163 B.1. Annotations pour indiquer quelles méthodes sont couvertes par un test .......................... 168 viii Liste des exemples 1.1. Tester les opérations sur les tableaux .......................................................................... 1 1.2. Utiliser print pour tester les opérations sur les tableaux .................................................. 1 1.3. Comparer les valeurs attendues et constatées pour tester les opérations sur les tableaux ......... 2 1.4. Utiliser une fonction d'assertion pour tester les opérations sur les tableaux .......................... 2 4.1. Tester des opérations de tableau avec PHPUnit ............................................................. 7 4.2. Utiliser l'annotation depends pour exprimer des dépendances ...................................... 8 4.3. Exploiter les dépendances entre les tests ...................................................................... 8 4.4. Utiliser un fournisseur de données qui renvoie un tableau de tableaux .............................. 10 4.5. Utiliser un fournisseur de données qui renvoie un objet Iterator ...................................... 11 4.6. La classe CsvFileIterator ......................................................................................... 11 4.7. Utiliser l'annotation expectedException ................................................................... 12 4.8. Utiliser les annotations expectedExceptionMessage et expectedExceptionCode ............ 13 4.9. Attendre une exception qui doit être levée par le code testé ........................................... 14 4.10. Approche alternative pour tester des exceptions ......................................................... 16 4.11. Attendre une erreur PHP en utilisant expectedException ........................................... 16 4.12. Tester des valeurs de retour d'un code source qui utilise des erreurs PHP ........................ 17 4.13. Tester la sortie écran d'une fonction ou d'une méthode ................................................ 18 4.14. Utilisation de assertArrayHasKey() .......................................................................... 19 4.15. Utilisation de assertClassHasAttribute() .................................................................... 20 4.16. Utilisation de assertClassHasStaticAttribute() ............................................................ 21 4.17. Utilisation de assertContains() ................................................................................ 22 4.18. Utilisation de assertContains() ................................................................................ 23 4.19. Utilisation de assertContainsOnly() ......................................................................... 24 4.20. Utilisation de assertCount() .................................................................................... 25 4.21. Utilisation de assertEmpty() ................................................................................... 26 4.22. Utilisation de assertEqualXMLStructure() ................................................................. 27 4.23. Utilisation de assertEquals() ................................................................................... 29 4.24. Utilisation de assertEquals() avec des nombres à virgule flottante .................................. 31 4.25. Utilisation de assertEquals() avec des objets DOMDocument ....................................... 32 4.26. Utilisation de assertEquals() avec des objets ............................................................. 33 4.27. Utilisation de assertEquals() avec des tableaux .......................................................... 34 4.28. Utilisation de assertFalse() ..................................................................................... 35 4.29. Utilisation de assertFileEquals() .............................................................................. 36 4.30. Utilisation de assertFileExists() ............................................................................... 37 4.31. Utilisation de assertGreaterThan() ........................................................................... 38 4.32. Utilisation de assertGreaterThanOrEqual() ................................................................ 39 4.33. Utilisation de assertInstanceOf() ............................................................................. 40 4.34. Utilisation de assertInternalType() ........................................................................... 41 4.35. Utilisation de assertJsonFileEqualsJsonFile() ............................................................. 42 4.36. Utilisation de assertJsonStringEqualsJsonFile() .......................................................... 43 4.37. Utilisation de assertJsonStringEqualsJsonString() ....................................................... 43 4.38. Utilisation de assertLessThan() ............................................................................... 45 4.39. Utilisation de assertLessThanOrEqual() .................................................................... 45 4.40. Utilisation de assertNull() ...................................................................................... 46 4.41. Utilisation de assertObjectHasAttribute() .................................................................. 47 4.42. Utilisation de assertRegExp() ................................................................................. 48 4.43. Utilisation de assertStringMatchesFormat() ............................................................... 49 4.44. Utilisation de assertStringMatchesFormatFile() .......................................................... 50 4.45. Utilisation de assertSame() .................................................................................... 51 4.46. Utilisation de assertSame() avec des objets ............................................................... 52 4.47. Utilisation de assertSelectCount() ............................................................................ 53 4.48. Utilisation de assertSelectEquals() ........................................................................... 55 4.49. Utilisation de assertSelectRegExp() ......................................................................... 57 4.50. Utilisation de assertStringEndsWith() ....................................................................... 58 4.51. Utilisation de assertStringEqualsFile() ...................................................................... 59 Manuel PHPUnit ix 4.52. Utilisation de assertStringStartsWith() ...................................................................... 60 4.53. Utilisation de assertTag() ....................................................................................... 62 4.54. Utilisation de assertThat() ...................................................................................... 63 4.55. Utilisation de assertTrue() ...................................................................................... 65 4.56. Utilisation de assertXmlFileEqualsXmlFile() ............................................................. 66 4.57. Utilisation de assertXmlStringEqualsXmlFile() .......................................................... 67 4.58. Utilisation de assertXmlStringEqualsXmlString() ....................................................... 69 6.1. Using setUp() to create the stack fixture .................................................................... 75 6.2. Exemple montrant toutes les méthodes canevas disponibles ........................................... 76 6.3. Partager les fixtures entre les tests d'une série de tests .................................................. 78 7.1. Composer une suite de tests en utilisant la configuration XML ....................................... 83 7.2. Composer une suite de tests en utilisant la configuration XML ....................................... 83 9.1. Signaler un test comme incomplet ........................................................................... 103 9.2. Sauter un test ...................................................................................................... 104 9.3. Sauter des cas de tests en utilisant requires ............................................................ 106 10.1. La classe que nous voulons bouchonner ................................................................. 107 10.2. Bouchonner un appel de méthode pour retourner une valeur fixée ................................ 108 10.3. Utiliser l'API Mock Builder pour configurer la classe de doublure de test générée. .......... 109 10.4. Bouchonner un appel de méthode pour renvoyer un des paramètres .............................. 109 10.5. Bouchonner un appel de méthode pour renvoyer une référence de l'objet bouchon. .......... 110 10.6. Bouchonner un appel de méthode pour retourner la valeur à partir d'un mappage ............ 110 10.7. Bouchonner un appel de méthode pour retourner une valeur à partir d'un rappel .............. 111 10.8. Bouchonner un appel de méthode pour retourner une liste de valeurs dans l'ordre indiqué ..................................................................................................................... 112 10.9. Bouchonner un appel de méthode pour lever une exception ........................................ 112 10.10. Les classes Sujet et Observateur qui sont une partie du système testé .......................... 113 10.11. Tester qu'une méthode est appelée une fois et avec un paramètre indiqué ..................... 114 10.12. Tester qu'une méthode est appelée avec un nombre de paramètres contraints de différentes manières .................................................................................................... 115 10.13. Tester les méthodes concrêtes d'une classe abstraite ................................................ 116 10.14. Bouchonner un web service ................................................................................ 116 10.15. Une classe qui interagit avec le système de fichiers ................................................. 118 10.16. Tester une classe qui interagoit avec le système de fichiers ....................................... 118 10.17. Simuler le système de fichiers dans un test pour une classe qui interagit avec le système de fichiers ................................................................................................................. 119 12.1. Tests pour la classe CompteBancaire ..................................................................... 123 12.2. Code nécessaire pour que le test testBalanceEstInitialementAZero() réussisse ................ 124 12.3. La classe CompteBancaire complète ...................................................................... 124 12.4. La classe CompteBancaire avec des vérifications de conception par contrat ................... 125 13.1. Spécification pour la classe JeuDeBowling .............................................................. 128 14.1. Test manquant pour atteindre la couverture de code complète ..................................... 134 14.2. Tests qui indiquent quelle(s) méthode(s) ils veulent couvrir ........................................ 135 14.3. Un test qui indique qu'aucune méthode ne doit être couverte ...................................... 136 14.4. Utiliser les annotations codeCoverageIgnore, codeCoverageIgnoreStart et codeCoverageIgnoreEnd ............................... 137 14.5. ......................................................................................................................... 138 16.1. La classe Calculateur .......................................................................................... 141 16.2. La classe Calculateur avec des annotations assert .................................................. 142 16.3. La classe JeuDeBowlingTest ................................................................................ 144 16.4. Le squelette généré de la classe JeuDeBowling ........................................................ 144 17.1. Exemple d'utilisation de PHPUnit_Extensions_Selenium2TestCase .............................. 146 17.2. Exemple d'utilisation de PHPUnit_Extensions_SeleniumTestCase ............................... 148 17.3. Faire une capture d'écran quand un test échoue ........................................................ 149 17.4. Régler la configuration de multiples navigateurs ...................................................... 150 17.5. Utiliser un répertoire de fichiers Selenese/HTML comme tests .................................... 153 19.1. Les méthodes assertTrue() et isTrue() de la classe PHPUnit_Framework_Assert ............. 158 19.2. La classe PHPUnit_Framework_Constraint_IsTrue ................................................... 159 19.3. Un simple moniteur de test .................................................................................. 159 Manuel PHPUnit x 19.4. Le décorateur RepeatedTest .................................................................................. 160 19.5. Un test dirigé par les données ............................................................................... 161 1 Chapitre 1. Automatiser les tests Même les bons programmeurs font des erreurs. La différence entre un bon programmeur et un mauvais est que le bon programmeur utilise des tests pour détecter ses erreurs dès que possible. Plus vite vous faites des tests pour trouver une erreur, plus grandes sont vos chances de la trouver et moins la recherche et la correction vous coûteront cher. Ceci explique pourquoi repousser les tests juste avant la livraison d'un logiciel est tellement problématique. La plupart des erreurs ne seront pas trouvées du tout et le coût pour corriger celles qui auront été trouvées sera tel que vous devrez faire des choix car vous ne pourrez pas les corriger toutes. Tester avec PHPUnit n'est pas une activité totalement différente de ce vous faites probablement déjà. C'est juste une façon différente de la réaliser. La différence se situe entre tester, c'est-à-dire, contrôler que votre programme se comporte de la façon attendue, et réaliser une batterie de tests, morceaux de code exécutables qui testent automatiquement la justesse de parties (unités) du logiciel. Ces morceaux de code exécutables sont appelés tests unitaires. Dans ce chapitre, nous partirons du simple code de test à base de print pour aboutir au test totalement automatisé. Imaginez qu'il vous a été demandé de tester le array de PHP (tableau). Parmi les fonctionnalités à tester se trouve la fonction count(). Pour un tableau nouvellement créé, nous attendons que la fonction count() retourne 0. Après avoir ajouté un élément, count() doit retourner 1. Exemple 1.1, « Tester les opérations sur les tableaux » montre ce que nous voulons tester. Exemple 1.1. Tester les opérations sur les tableaux <?php $fixture = array(); // $fixture doit être vide. $fixture[] = 'element'; // $fixture doit contenir un élément. ?> Un moyen vraiment simple de contrôler que nous obtenons les résultats que nous attendons consiste à afficher le résultat de count() avant et après l'ajout de l'élément (voir Exemple 1.2, « Utiliser print pour tester les opérations sur les tableaux »). Si nous obtenons 0 puis 1, array et count() se comportent comme espéré. Exemple 1.2. Utiliser print pour tester les opérations sur les tableaux <?php $fixture = array(); print count($fixture) . " "; $fixture[] = 'element'; print count($fixture) . " "; ?> 0 1 Maintenant, nous aimerions passer de tests qui nécessitent une interprétation manuelle à des tests qui sont exécutés automatiquement. Dans Exemple 1.3, « Comparer les valeurs attendues et constatées pour tester les opérations sur les tableaux », nous écrivons la comparaison entre les valeurs attendues et constatées dans le code de test et nous affichons ok si ces valeurs sont égales. Si jamais nous voyons un message not ok, nous savons que quelque chose ne va pas. Automatiser les tests 2 Exemple 1.3. Comparer les valeurs attendues et constatées pour tester les opérations sur les tableaux <?php $fixture = array(); print count($fixture) == 0 ? "ok " : "not ok "; $fixture[] = 'element'; print count($fixture) == 1 ? "ok " : "not ok "; ?> ok ok Nous factorisons maintenant la comparaison des valeurs attendues et constatées dans une fonction qui lève une Exception quand il y a non correspondance (Exemple 1.4, « Utiliser une fonction d'assertion pour tester les opérations sur les tableaux »). Ceci nous offre deux avantages : l'écriture de tests devient plus facile et nous n'avons de message que quand il y a un problème. Exemple 1.4. Utiliser une fonction d'assertion pour tester les opérations sur les tableaux <?php $fixture = array(); assertTrue(count($fixture) == 0); $fixture[] = 'element'; assertTrue(count($fixture) == 1); function assertTrue($condition) { if (!$condition) { throw new Exception('Assertion en échec.'); } } ?> Le test est maintenant complètement automatisé. Au lieu de simplement tester comme nous le faisions dans notre première version, avec cette version, nous avons un test automatisé. L'objectif en utilisant des tests automatisés et de faire moins d'erreurs. Tant que votre code ne sera pas parfait, même avec d'excellents tests, vous constaterez certainement une réduction drastique des défauts dès que vous commencerez à utiliser des tests automatisés. Les tests automatisés vous donnent une confiance justifiée en votre code. Vous pouvez mettre à profit cette confiance pour faire évoluer de façon plus audacieuse votre conception (refactoring), mieux collaborer avec les autres membres de votre équipe (tests communs à l'équipe), améliorer les relations avec vos clients et rentrer chez vous tous les soirs avec la preuve que le système est meilleur maintenant qu'il ne l'était ce matin grâce à vos efforts. 3 Chapitre 2. Objectifs de PHPUnit Arrivé à ce point, nous n'avons que deux tests pour le array de PHP et la fonction count(). Quand nous commencerons à tester les nombreuses fonctions array_*() que PHP offre, nous devrons écrire un test pour chacune d'elles. Nous pourrions écrire l'infrastructure pour tous ces tests à partir de zéro. Cependant, il est bien mieux d'écrire une infrastructure de tests une fois pour toute puis de n'écrire ensuite que les parties spécifiques à chaque test. PHPUnit est ce type d'infrastructure. Un framework comme PHPUnit doit résoudre une série de contraintes, certaines d'entre elles semblant toujours contradictoires avec d'autres. Les tests doivent être à la fois : Faciles à apprendre et à écrire. S'il est difficile à apprendre comment écrire les tests, les développeurs n'apprendront pas à les écrire. Facile à écrire. Si les tests ne sont pas faciles à écrire, les développeurs ne les écriront pas. Faciles à lire. Le code de test ne doit pas contenir de surcharge supplémentaire de façon à ce que le test lui-même ne soit pas perdu au milieu du bruit qui l'entoure. Faciles à exécuter. Les tests doivent s'exécuter en cliquant simplement sur un bouton et afficher leurs résultats d'un façon claire et non ambiguë. Rapides à exécuter. Les tests doivent s'exécuter rapidement afin qu'ils puissent être lancés des centaines ou des milliers de fois par jour. Indépendants. Les tests ne doivent pas interagir les uns avec les autres. Si l'ordre dans lequel les tests sont lancés change, les résultats, eux, ne doivent pas être modifiés. Composables. Nous devons pouvoir lancer n'importe quel nombre ou combinaison de tests ensembles. C'est un corollaire de l'indépendance. Il existe deux antagonismes majeurs entre ces contraintes : Faciles à apprendre à écrire contre faciles à écrire. Les tests ne nécessitent pas généralement toute la souplesse d'un langage de programmation. De nombreux outils de test fournissent leur propre langage de script qui n'inclut que le minimum nécessaire en matière de fonctionnalité pour écrire des tests. Les tests produits sont faciles à lire et à écrire car ils ne comportent pas de bruit susceptible de vous distraire de leur contenu. Cependant, apprendre encore un autre langage de programmation et une série d'outils de programmation est malcommode et encombre l'esprit. Indépendants contre rapides à exécuter. Si vous voulez que les résultats d'un test n'aient pas d'effets sur les résultats d'un autre, chaque test doit créer l'état complet du monde avant de commencer à s'exécuter, puis restaurer l'état initial du monde quand il a fini. Cependant, configurer le monde peut prendre un long moment : par exemple, se connecter à une base de données et l'initialiser dans un état connu en utilisant des données réalistes. PHPUnit tente de résoudre ces conflits en utilisant PHP comme langage de test. Quelquefois, la pleine puissance de PHP est disproportionnée pour écrire de petits tests d'une ligne, mais en utilisant PHP, nous tirons profit de toute l'expérience et des outils dont disposent déjà les programmeurs. Puisque Objectifs de PHPUnit 4 nous essayons de convaincre des testeurs réticents, limiter les obstacles pour écrire ces premiers tests est particulièrement important. PHPUnit privilégie plutôt l'indépendance à la rapidité d'exécution. Les tests indépendants sont précieux car ils fournissent des retours de grande qualité. Vous n'obtenez pas un rapport avec un tas d'erreurs de test qui sont en fait provoqués par l'échec d'un test au début de la série et qui a laissé le monde en désordre pour les autres tests. Cette orientation en faveur des tests indépendants encourage les conceptions dotées d'un grand nombre d'objets simples. Chaque objet peut être testé rapidement de manière indépendante. Il en résulte de meilleures conceptions et des tests plus rapides. PHPUnit part du postulat que la plupart des tests réussissent et qu'il n'est pas nécessaire de rendre compte des détails des tests réussis. Quand un test échoue, cela vaut la peine de le signaler et de détailler le problème. La grande majorité des tests doit réussir et aucun commentaire n'est intéressant si ce n'est le nombre de tests exécutés. Dans la pratique, ce postulat est implémenté dans les classes produisant les rapports et pas dans le noyau de PHPUnit. Quand les résultats d'une série de tests sont rapportés, vous voyez combien de tests ont été exécutés mais vous ne verrez les détails que pour ceux qui ont échoué. Les tests sont supposés être à fine maille, ne testant qu'un seul aspect d'un seul objet. Par conséquent, la première fois qu'un test échoue, l'exécution du test s'interrompt et PHPUnit rapporte le problème. Tester en exécutant de nombreux petits tests est un art. Les tests à fine maille améliore la conception générale du système. Lorsque vous testez un objet avec PHPUnit, vous ne le faites que via l'interface publique de l'objet. Tester en ne se basant que sur le comportement publiquement visible encourage à affronter et résoudre les problèmes de conception difficiles très tôt, avant que les conséquences d'une mauvaise conception ne puissent infecter de grandes parties du système. 5 Chapitre 3. Installer PHPUnit PHPUnit doit être installé en utilisant l'installateur PEAR, la colonne vertébrale du dépôt d'extensions et d'applications PHP (PHP Extension and Application Repository) [http://pear.php.net/] qui apporte un système de distribution pour les paquets PHP. Attention Selon votre distribution de système d'exploitation et/ou votre environnement PHP, vous pouvez avoir besoin d'installer PEAR ou de mettre à jour votre installation existante de PEAR avant de pouvoir suivre les instructions de ce chapitre. sudo pear upgrade PEAR suffit habituellement pour mettre à jour une installation PEAR existante. Le manuel PEAR [http://pear.php.net/manual/en/installation.getting.php] explique comment réaliser une nouvelle installation de PEAR. Note PHPUnit 3.7 nécessite PHP 5.3.3 (ou ultérieur) mais PHP 5.4.0 (ou ultérieur) est fortement recommandé. PHP_CodeCoverage, la bibliothèque qui est utilisée par PHPUnit pour rassembler et traiter les informations de couverture de code, dépend de Xdebug 2.0.5 (ou ultérieure) mais Xdebug 2.2.0 (ou ultérieur) est fortement recommandé. Les deux commandes suivantes (que vous aurez peut-être à exécuter en tant que super-administrateur, i.e. root )sont tout ce qui est nécessaire pour installer PHPUnit en utilisant l'installateur PEAR: pear install pear.phpunit.de/PHPUnitpear config-set auto_discover 1 pear install pear.phpunit.de/PHPUnit Les paquets facultatifs suivants sont disponibles : DbUnit Portage de DbUnit pour PHP/PHPUnit destiné à gérer les tests interagissant avec des bases de données. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/DbUnit PHPUnit_Selenium Intégration de Selenium RC pour PHPUnit. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_Selenium PHPUnit_Story Lanceur de tests basés sur des histoires pour les développements dirigés par le comportement (Behavior- Driven Development) avec PHPUnit. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_Story PHPUnit_SkeletonGenerator Outil qui permet de générer des squelettes de classes de test à partir des classes du code de production et vice versa. Installer PHPUnit 6 Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_SkeletonGenerator PHPUnit_TestListener_DBUS Un moniteur de tests qui envoie des événements à DBUS. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_TestListener_DBUS PHPUnit_TestListener_XHProf Un moniteur de tests qui utilise XHProf pour profiler automatiquement le code testé. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_TestListener_XHProf PHPUnit_TicketListener_Fogbugz Un moniteur de tickets qui interagit avec l'API d'incidents de FogBugz. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_TicketListener_Fogbugz PHPUnit_TicketListener_GitHub Un moniteur de tickets qui interagit avec l'API d'incidents de GitHub. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_TicketListener_GitHub PHPUnit_TicketListener_GoogleCode Un moniteur de tickets qui interagit avec l'API d'incidents de Google Code. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_TicketListener_GoogleCode PHPUnit_TicketListener_Trac Un moniteur de tickets qui interagit avec l'API d'incidents de Trac. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_TicketListener_Trac PHP_Invoker Une classe utilitaire pour invoquer des appels avec un délai d'expiration. Ce paquet est nécessaire pour mettre en oeuvre des dépassements de délais pour les tests en mode strict. Ce paquet peut être installé en utilisant la commande suivante : pear install phpunit/PHP_Invoker Après l'installation, vous trouverez les fichiers du code source de PHPUnit dans votre répertoire local PEAR; le chemin d'accès est habituellement /usr/lib/php/PHPUnit. 7 Chapitre 4. Ecrire des tests pour PHPUnit Exemple 4.1, « Tester des opérations de tableau avec PHPUnit » montre comment nous pouvons écrire des tests en utilisant PHPUnit pour contrôler les opérations PHP sur les tableaux. L'exemple introduit les conventions et les étapes de base pour écrire des tests avec PHPUnit: 1. Les tests pour une classe Classe vont dans une classe ClasseTest. 2. ClasseTest hérite (la plupart du temps) de PHPUnit_Framework_TestCase. 3. 4. A l'intérieur des méthodes de test, des méthodes d'assertion telles que assertEquals() (voir la section intitulée « Assertions ») sont utilisées pour affirmer qu'une valeur constatée correspond à une valeur attendue. Exemple 4.1. Tester des opérations de tableau avec PHPUnit <?php class PileTest extends PHPUnit_Framework_TestCase { public function testerPushEtPop() { $pile = array(); $this->assertEquals(0, count($pile)); array_push($pile, 'foo'); $this->assertEquals('foo', $pile[count($pile)-1]); $this->assertEquals(1, count($pile)); $this->assertEquals('foo', array_pop($pile)); $this->assertEquals(0, count($pile)); } } ?> A chaque fois que vous avez la tentation de saisir quelque chose dans une instruction print ou dans une expression de débogage, écrivez le plutôt dans un test. ?Martin Fowler Dépendances des tests Les tests unitaires sont avant tout écrits comme étant une bonne pratique destinée à aider les développeurs à identifier et corriger les bugs, à refactoriser le code et à servir de documentation pour une unité du logiciel testé. Pour obtenir ces avantages, les tests unitaires doivent idéalement couvrir tous les chemins possibles du programme. Un test unitaire couvre usuellement un unique chemin particulier d'une seule fonction ou méthode. Cependant, une méthode de test n'est pas obligatoirement une entité encapsulée et indépendante. Souvent, il existe des dépendances implicites entre les méthodes de test, cachées dans l'implémentation du scénario d'un test. ?Adrian Kuhn et. al. PHPUnit gère la déclaration de dépendances explicites entre les méthodes de test. De telles dépendances ne définissent pas l'ordre dans lequel les méthodes de test doivent être exécutées, mais elles permettent l'envoi d'une instance d'un composant de test par un producteur à des consommateurs qui en dépendent. Ecrire des tests pour PHPUnit 8 ? Un producteur est une méthode de test qui cède ses éléments testées comme valeur de sortie. ? Un consommateur est une méthode de test qui dépend d'un ou plusieurs producteurs et de leurs valeurs de retour. Exemple 4.2, « Utiliser l'annotation depends pour exprimer des dépendances » montre comment utiliser l'annotation depends pour exprimer des dépendances entre des méthodes de test. Exemple 4.2. Utiliser l'annotation depends pour exprimer des dépendances <?php class PileTest extends PHPUnit_Framework_TestCase { public function testEmpty() { $pile = array(); $this->assertEmpty($pile); return $pile; } /** * depends testEmpty */ public function testPush(array $pile) { array_push($pile, 'foo'); $this->assertEquals('foo', $pile[count($pile)-1]); $this->assertNotEmpty($pile); return $pile; } /** * depends testPush */ public function testPop(array $pile) { $this->assertEquals('foo', array_pop($pile)); $this->assertEmpty($pile); } } ?> Dans l'exemple ci-dessus, le premier test, testVide(), crée un nouveau tableau et affirme qu'il est vide. Le test renvoie ensuite la fixture comme résultat. Le deuxième test, testPush(), dépend de testVide() et reçoit le résultat de ce test dont il dépend comme argument. Enfin, testPop() dépend de testPush(). Pour localiser rapidement les défauts, nous voulons que notre attention soit retenue par les tests en échecs pertinents. C'est pourquoi PHPUnit saute l'exécution d'un test quand un test dont il dépend a échoué. Ceci améliore la localisation des défauts en exploitant les dépendances entre les tests comme montré dans Exemple 4.3, « Exploiter les dépendances entre les tests ». Exemple 4.3. Exploiter les dépendances entre les tests <?php class DependencyFailureTest extends PHPUnit_Framework_TestCase { public function testUn() { $this->assertTrue(FALSE); Ecrire des tests pour PHPUnit 9 } /** * depends testUn */ public function testDeux() { } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. FS Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) DependencyFailureTest::testUn Failed asserting that false is true. /home/sb/DependencyFailureTest.php:6 There was 1 skipped test: 1) DependencyFailureTest::testDeux This test depends on "DependencyFailureTest::testUn" to pass. FAILURES! Tests: 1, Assertions: 1, Failures: 1, Skipped: 1.phpunit --verbose DependencyFailureTest PHPUnit 3.7.0 by Sebastian Bergmann. FS Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) DependencyFailureTest::testUn Failed asserting that false is true. /home/sb/DependencyFailureTest.php:6 There was 1 skipped test: 1) DependencyFailureTest::testDeux This test depends on "DependencyFailureTest::testUn" to pass. FAILURES! Tests: 1, Assertions: 1, Failures: 1, Skipped: 1. Un test peut avoir plusieurs annotations depends. PHPUnit ne change pas l'ordre dans lequel les tests sont exécutés, vous devez donc vous assurer que les dépendances d'un test peuvent effectivement être utilisables avant que le test ne soit lancé. Fournisseur de données Une méthode de test peut recevoir des arguments arbitraires. Ces arguments doivent être fournis par une méthode fournisseuse de données (provider() dans Exemple 4.4, « Utiliser un fournisseur Ecrire des tests pour PHPUnit 10 de données qui renvoie un tableau de tableaux »). La méthode fournisseuse de données à utiliser est indiquée dans l'annotation dataProvider annotation. Une méthode fournisseuse de données doit être public et retourne, soit un tableau de tableaux, soit un objet qui implémente l'interface Iterator et renvoie un tableau pour chaque itération. Pour chaque tableau qui est une partie de l'ensemble, la méthode de test sera appelée avec comme arguments le contenu du tableau. Exemple 4.4. Utiliser un fournisseur de données qui renvoie un tableau de tableaux <?php class DataTest extends PHPUnit_Framework_TestCase { /** * dataProvider fournisseur */ public function testAdditionne($a, $b, $c) { $this->assertEquals($c, $a + $b); } public function fournisseur() { return array( array(0, 0, 0), array(0, 1, 1), array(1, 0, 1), array(1, 1, 3) ); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. ...F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) DataTest::testAdditionne with data set #3 (1, 1, 3) Failed asserting that 2 matches expected 3. /home/sb/DataTest.php:9 FAILURES! Tests: 4, Assertions: 4, Failures: 1.phpunit DataTest PHPUnit 3.7.0 by Sebastian Bergmann. ...F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) DataTest::testAdditionne with data set #3 (1, 1, 3) Failed asserting that 2 matches expected 3. /home/sb/DataTest.php:9 Ecrire des tests pour PHPUnit 11 FAILURES! Tests: 4, Assertions: 4, Failures: 1. Exemple 4.5. Utiliser un fournisseur de données qui renvoie un objet Iterator <?php require 'CsvFileIterator.php'; class DataTest extends PHPUnit_Framework_TestCase { /** * dataProvider fournisseur */ public function testAdditionne($a, $b, $c) { $this->assertEquals($c, $a + $b); } public function fournisseur() { return new CsvFileIterator('data.csv'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. ...F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) DataTest::testAdditionne with data set #3 ('1', '1', '3') Failed asserting that 2 matches expected '3'. /home/sb/DataTest.php:11 FAILURES! Tests: 4, Assertions: 4, Failures: 1.phpunit DataTest PHPUnit 3.7.0 by Sebastian Bergmann. ...F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) DataTest::testAdditionne with data set #3 ('1', '1', '3') Failed asserting that 2 matches expected '3'. /home/sb/DataTest.php:11 FAILURES! Tests: 4, Assertions: 4, Failures: 1. Exemple 4.6. La classe CsvFileIterator <?php class CsvFileIterator implements Iterator { protected $fichier; protected $key = 0; Ecrire des tests pour PHPUnit 12 protected $current; public function __construct($fichier) { $this->fichier = fopen($fichier, 'r'); } public function __destruct() { fclose($this->fichier); } public function rewind() { rewind($this->fichier); $this->current = fgetcsv($this->fichier); $this->key = 0; } public function valid() { return !feof($this->fichier); } public function key() { return $this->key; } public function current() { return $this->current; } public function next() { $this->current = fgetcsv($this->fichier); $this->key++; } } ?> Note Quand un test reçoit des entrées à la fois d'une méthode dataProvider et d'un ou plusieurs tests dont il depends, les arguments provenant du fournisseur de données arriveront avant ceux des tests dont il dépend. Note Quand un test dépend d'un test qui utilise des fournisseurs de données, le test dépendant sera exécuté quand le test dont il dépend réussira pour au moins un jeu de données. Le résultat d'un test qui utilise des fournisseurs de données ne peut pas être injecté dans un test dépendant. Note Tous les fournisseurs de données sont exécutés avant le premier appel à la fonction setUp. De ce fait, vous ne pouvez accéder à aucune variable créée à cet endroit depuis un fournisseur de données. Tester des exceptions Exemple 4.7, « Utiliser l'annotation expectedException » montre comment utiliser l'annotation expectedException pour tester si une exception est levée à l'intérieur du code testé. Exemple 4.7. Utiliser l'annotation expectedException <?php Ecrire des tests pour PHPUnit 13 class ExceptionTest extends PHPUnit_Framework_TestCase { /** * expectedException InvalidArgumentException */ public function testException() { } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ExceptionTest::testException Expected exception InvalidArgumentException FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit ExceptionTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ExceptionTest::testException Expected exception InvalidArgumentException FAILURES! Tests: 1, Assertions: 1, Failures: 1. Additionnellement, vous pouvez utiliser expectedExceptionMessage et expectedExceptionCode en combinaison de expectedException pour tester le message d'exception et le code d'exception comme montré dans Exemple 4.8, « Utiliser les annotations expectedExceptionMessage et expectedExceptionCode ». Exemple 4.8. Utiliser les annotations expectedExceptionMessage et expectedExceptionCode <?php class ExceptionTest extends PHPUnit_Framework_TestCase { /** * expectedException InvalidArgumentException * expectedExceptionMessage Message correct */ public function testExceptionPossedeLeBonMessage() { throw new InvalidArgumentException('Un message', 10); } /** * expectedException InvalidArgumentException Ecrire des tests pour PHPUnit 14 * expectedExceptionCode 20 */ public function testExceptionPossedeLeBonCode() { throw new InvalidArgumentException('Un message', 10); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. FF Time: 0 seconds, Memory: 3.00Mb There were 2 failures: 1) ExceptionTest::testExceptionPossedeLeBonMessage Failed asserting that exception message 'Un Message' contains 'Message correct'. 2) ExceptionTest::testExceptionPossedeLeBonCode Failed asserting that expected exception code 20 is equal to 10. FAILURES! Tests: 2, Assertions: 4, Failures: 2.phpunit ExceptionTest PHPUnit 3.7.0 by Sebastian Bergmann. FF Time: 0 seconds, Memory: 3.00Mb There were 2 failures: 1) ExceptionTest::testExceptionPossedeLeBonMessage Failed asserting that exception message 'Un Message' contains 'Message correct'. 2) ExceptionTest::testExceptionPossedeLeBonCode Failed asserting that expected exception code 20 is equal to 10. FAILURES! Tests: 2, Assertions: 4, Failures: 2. Alternativement, vous pouvez utiliser la méthode setExpectedException() pour indiquer l'exception attendue comme montré dans Exemple 4.9, « Attendre une exception qui doit être levée par le code testé ». Exemple 4.9. Attendre une exception qui doit être levée par le code testé <?php class ExceptionTest extends PHPUnit_Framework_TestCase { public function testException() { $this->setExpectedException('InvalidArgumentException'); } public function testExceptionPossedeLeBonMessage() { $this->setExpectedException( 'InvalidArgumentException', 'Message correct' Ecrire des tests pour PHPUnit 15 ); throw new InvalidArgumentException('Un message', 10); } public function testExceptionPossedeLeBonCode() { $this->setExpectedException( 'InvalidArgumentException', 'Message correct', 20 ); throw new InvalidArgumentException('Message correct', 10); } }?> PHPUnit 3.7.0 by Sebastian Bergmann. FFF Time: 0 seconds, Memory: 3.00Mb There were 3 failures: 1) ExceptionTest::testException Expected exception InvalidArgumentException 2) ExceptionTest::testExceptionPossedeLeBonMessage Failed asserting that exception message 'Un message' contains 'Message correct'. 3) ExceptionTest::testExceptionPossedeLeBonCode Failed asserting that expected exception code 20 is equal to 10. FAILURES! Tests: 3, Assertions: 6, Failures: 3.phpunit ExceptionTest PHPUnit 3.7.0 by Sebastian Bergmann. FFF Time: 0 seconds, Memory: 3.00Mb There were 3 failures: 1) ExceptionTest::testException Expected exception InvalidArgumentException 2) ExceptionTest::testExceptionPossedeLeBonMessage Failed asserting that exception message 'Un message' contains 'Message correct'. 3) ExceptionTest::testExceptionPossedeLeBonCode Failed asserting that expected exception code 20 is equal to 10. FAILURES! Tests: 3, Assertions: 6, Failures: 3. Tableau 4.1, « Méthodes pour tester des exceptions » montre les méthodes fournies pour tester des exceptions. Ecrire des tests pour PHPUnit 16 Tableau 4.1. Méthodes pour tester des exceptions Méthode Signification void setExpectedException(string $nomDeLException[, string $messageDeLException = '', integer $codeDeLException = NULL]) Indiquer le $nomDeLException attendue, le $messageDeLException et le $codeDeLException. String getExpectedException() Retourne le nom de l'exception attendue. Vous pouvez également utiliser l'approche montrée dans Exemple 4.10, « Approche alternative pour tester des exceptions » pour tester des exceptions. Exemple 4.10. Approche alternative pour tester des exceptions <?php class ExceptionTest extends PHPUnit_Framework_TestCase { public function testException() { try { // ... Code qui devrait lever une exception ... } catch (InvalidArgumentException $attendu) { return; } $this->fail('Une exception attendue n'a pas été levée.'); } } ?> Si le code qui devrait lever une exception dans Exemple 4.10, « Approche alternative pour tester des exceptions » ne lève pas l'exception attendue, l'appel induit à fail() va interrompre le test et signaler un problème pour ce test. Si l'exception attendue est levée, le bloc catch sera exécuté et le test s'achèvera avec succès. Tester les erreurs PHP Par défaut, PHPUnit convertit les erreurs, avertissements et remarques PHP qui sont émises lors de l'exécution d'un test en exception. En utilisant ces exceptions, vous pouvez, par exemple, attendre d'un test qu'il produise une erreur PHP comme montré dans Exemple 4.11, « Attendre une erreur PHP en utilisant expectedException ». Exemple 4.11. Attendre une erreur PHP en utilisant expectedException <?php class ExpectedErrorTest extends PHPUnit_Framework_TestCase { /** expectedException PHPUnit_Framework_Error */ public function testEchecInclude() { include 'fichier_qui_n_existe_pas.php'; } } ?> Ecrire des tests pour PHPUnit 17 PHPUnit 3.7.0 by Sebastian Bergmann. . Time: 0 seconds, Memory: 5.25Mb OK (1 test, 1 assertion)phpunit ExpectedErrorTest PHPUnit 3.7.0 by Sebastian Bergmann. . Time: 0 seconds, Memory: 5.25Mb OK (1 test, 1 assertion) PHPUnit_Framework_Error_Notice et PHPUnit_Framework_Error_Warning représentent respectivement les remarques et les avertissements PHP. Note Vous devriez être aussi précis que possible lorsque vous testez des exceptions. Tester avec des classes qui sont trop génériques peut conduire à des effets de bord indésirables. C'est pourquoi tester la présence de la classe Exception avec expectedException ou setExpectedException() n'est plus autorisé. Quand les tests s'appuient sur des fonctions php qui déclenchent des erreurs comme fopen, il peut parfois être utile d'utiliser la suppression d'erreur lors du test. Ceci permet de contrôler les valeurs de retour en supprimant les remarques qui auraient conduit à une PHPUnit_Framework_Error_Notice de phpunit. Exemple 4.12. Tester des valeurs de retour d'un code source qui utilise des erreurs PHP <?php class ErrorSuppressionTest extends PHPUnit_Framework_TestCase { public function testEcritureFichier() { $writer = new FileWriter; $this->assertFalse($writer->ecrit('/non-accessible-en-ecriture/fichier', 'texte' } } class FileWriter { public function ecrit($fichier, $contenu) { $fichier = fopen($fichier, 'w'); if($fichier == false) { return false; } // ... } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. . Time: 1 seconds, Memory: 5.25Mb Ecrire des tests pour PHPUnit 18 OK (1 test, 1 assertion)phpunit ErrorSuppressionTest PHPUnit 3.7.0 by Sebastian Bergmann. . Time: 1 seconds, Memory: 5.25Mb OK (1 test, 1 assertion) Sans la suppression d'erreur, le test échouerait à rapporter fopen(/non-accessible-en- ecriture/fichier): failed to open stream: No such file or directory. Tester la sortie écran Quelquefois, vous voulez affirmer que l'exécution d'une méthode, par exemple, produit une sortie écran donnée (via echo ou print, par exemple). La classe PHPUnit_Framework_TestCase utilise la fonctionnalité de en tampon de PHP mise en tampon de la sortie écran [http://www.php.net/ manual/en/ref.outcontrol.php] de PHP pour fournir la fonctionnalité qui est nécessaire pour cela. Exemple 4.13, « Tester la sortie écran d'une fonction ou d'une méthode » montre comment utiliser la méthode expectOutputString() pour indiquer la sortie écran attendue. Si la sortie écran attendue n'est pas générée, le test sera compté comme étant en échec. Exemple 4.13. Tester la sortie écran d'une fonction ou d'une méthode <?php class OutputTest extends PHPUnit_Framework_TestCase { public function testFooAttenduFooObtenu() { $this->expectOutputString('foo'); print 'foo'; } public function testBarAttenduBazAttendu() { $this->expectOutputString('bar'); print 'baz'; } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. .F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) OutputTest::testBarAttenduBazObtenu Failed asserting that two strings are equal. --- Expected +++ Actual -'bar' +'baz' FAILURES! Ecrire des tests pour PHPUnit 19 Tests: 2, Assertions: 2, Failures: 1.phpunit OutputTest PHPUnit 3.7.0 by Sebastian Bergmann. .F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) OutputTest::testBarAttenduBazObtenu Failed asserting that two strings are equal. --- Expected +++ Actual -'bar' +'baz' FAILURES! Tests: 2, Assertions: 2, Failures: 1. Tableau 4.2, « Méthodes pour tester les sorties écran » montre les méthodes fournies pour tester les sorties écran Tableau 4.2. Méthodes pour tester les sorties écran Méthode Signification void expectOutputRegex(string $expressionRationnelle) Indique que l'on s'attend à ce que la sortie écran corresponde à une $expressionRationnelle. void expectOutputString(string $attenduString) Indique que l'on s'attend que la sortie écran soit égale à une $chaineDeCaracteresAttendue. bool setOutputCallback(callable $callback) Configure une fonction de rappel (callback) qui est utilisée, par exemple, formater la sortie écran effective. Note Merci de noter que PHPUnit absorbe toutes les sorties écran qui sont émises lors de l'exécution d'un test. En mode strict, un test qui produit une sortie écran échouera. Assertions Cette section liste les diverses méthodes d'assertion qui sont disponibles. assertArrayHasKey() assertArrayHasKey(mixed $clef, array $tableau[, string $message = '']) Rapporte une erreur identifiée par un $message si le $tableau ne contient pas la $clef. assertArrayNotHasKey() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.14. Utilisation de assertArrayHasKey() <?php Ecrire des tests pour PHPUnit 20 class TableauPossedeUneClefTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertArrayHasKey('foo', array('bar' => 'baz')); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) TableauPossedeUneClefTest::testEchec Failed asserting that an array has the key 'foo'. /home/sb/TableauPossedeUneClefTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit TableauPossedeUneClefTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) TableauPossedeUneClefTest::testEchec Failed asserting that an array has the key 'foo'. /home/sb/TableauPossedeUneClefTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertClassHasAttribute() assertClassHasAttribute(string $nomAttribut, string $nomClasse[, string $message = '']) Rapporte une erreur identifiée par un $message si $nomClasse::nomAttribut n'existe pas. assertClassNotHasAttribute() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.15. Utilisation de assertClassHasAttribute() <?php class ClassePossedeUnAttributTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertClassHasAttribute('foo', 'stdClass'); } } Ecrire des tests pour PHPUnit 21 ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ClassePossedeUnAttributTest::testEchec Failed asserting that class "stdClass" has attribute "foo". /home/sb/ClassePossedeUnAttributTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit ClassePossedeUnAttributTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ClassePossedeUnAttributTest::testEchec Failed asserting that class "stdClass" has attribute "foo". /home/sb/ClassePossedeUnAttributTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertClassHasStaticAttribute() assertClassHasStaticAttribute(string $nomAttribut, string $nomClasse[, string $message = '']) Rapporte une erreur identifiée par un $message si $nomClasse::nomAttribut n'existe pas. assertClassNotHasStaticAttribute() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.16. Utilisation de assertClassHasStaticAttribute() <?php class ClassePossedeUnAttributStatiqueTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertClassHasStaticAttribute('foo', 'stdClass'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Ecrire des tests pour PHPUnit 22 Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ClassHasStaticAttributeTest::testEchec Failed asserting that class "stdClass" has static attribute "foo". /home/sb/ClassePossedeUnAttributStatiqueTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit ClassePossedeUnAttributStatiqueTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ClassHasStaticAttributeTest::testEchec Failed asserting that class "stdClass" has static attribute "foo". /home/sb/ClassePossedeUnAttributStatiqueTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertContains() assertContains(mixed $aiguille, Iterator|array $meuleDeFoin[, string $message = '']) Rapporte une erreur identifiée par un $message si $aiguille n'est pas un élément de $meuleDeFoin. assertNotContains() est l'inverse de cette assertion et prend les mêmes arguments. assertAttributeContains() et assertAttributeNotContains() sont des enrobeurs de commodité qui utilisent l'attribut public, protected ou private d'une classe ou d'un objet comme meuleDeFoin. Exemple 4.17. Utilisation de assertContains() <?php class ContainsTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertContains(4, array(1, 2, 3)); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: Ecrire des tests pour PHPUnit 23 1) ContainsTest::testEchec Failed asserting that an array contains 4. /home/sb/ContainsTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit ContainsTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) ContainsTest::testEchec Failed asserting that an array contains 4. /home/sb/ContainsTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertContains(string $aiguille, string $meuleDeFoin[, string $message = '']) Rapporte une erreur identifiée par un $message si $aiguille n'est pas un sous chaîne de $meuleDeFoin. Exemple 4.18. Utilisation de assertContains() <?php class ContainsTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertContains('baz', 'foobar'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) ContainsTest::testEchec Failed asserting that 'foobar' contains "baz". /home/sb/ContainsTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit ContainsTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb Ecrire des tests pour PHPUnit 24 There was 1 failure: 1) ContainsTest::testEchec Failed asserting that 'foobar' contains "baz". /home/sb/ContainsTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertContainsOnly() assertContainsOnly(string $type, Iterator|array $meuleDeFoin[, boolean $estUnTypeNatif = NULL, string $message = '']) Rapporte une erreur identifiée par le $message si $meuleDeFoin ne contient pas que des variables du type $type. $estUnTypeNatif est un drapeau qui indique si $type est un type natif de PHP ou pas. assertNotContainsOnly() est l'inverse de cette assertion et prend les mêmes arguments. assertAttributeContainsOnly() et assertAttributeNotContainsOnly() sont des enrobeurs de commodité qui utilisent un attribut public, protected ou private d'une classe ou d'un objet en tant que valeur constatée. Exemple 4.19. Utilisation de assertContainsOnly() <?php class ContainsOnlyTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertContainsOnly('string', array('1', '2', 3)); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) ContainsOnlyTest::testEchec Failed asserting that Array ( 0 => '1' 1 => '2' 2 => 3 ) contains only values of type "string". /home/sb/ContainsOnlyTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit ContainsOnlyTest PHPUnit 3.7.0 by Sebastian Bergmann. Ecrire des tests pour PHPUnit 25 F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) ContainsOnlyTest::testEchec Failed asserting that Array ( 0 => '1' 1 => '2' 2 => 3 ) contains only values of type "string". /home/sb/ContainsOnlyTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertCount() assertCount($nombreAttendu, $meuleDeFoin[, string $message = '']) Rapporte une erreur identifiée par $message si le nombre d'éléments dans $meuleDeFoin n'est pas $nombreAttendu. assertNotCount() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.20. Utilisation de assertCount() <?php class CountTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertCount(0, array('foo')); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) CountTest::testEchec Failed asserting that actual size 1 matches expected size 0. /home/sb/CountTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit CountTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb Ecrire des tests pour PHPUnit 26 There was 1 failure: 1) CountTest::testEchec Failed asserting that actual size 1 matches expected size 0. /home/sb/CountTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertEmpty() assertEmpty(mixed $constate[, string $message = '']) Rapporte une erreur identifiée par $message si $constate n'est pas vide. assertNotEmpty() est l'inverse de cette assertion et prend les mêmes arguments. assertAttributeEmpty() et assertAttributeNotEmpty() sont des enrobeurs de commodité qui peuvent être appliqués à un attribut public, protected ou private d'une classe ou d'un objet. Exemple 4.21. Utilisation de assertEmpty() <?php class VideTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertEmpty(array('foo')); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) VideTest::testEchec Failed asserting that an array is empty. /home/sb/VideTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit VideTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) VideTest::testEchec Failed asserting that an array is empty. Ecrire des tests pour PHPUnit 27 /home/sb/VideTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertEqualXMLStructure() assertEqualXMLStructure(DOMElement $elementAttendu, DOMElement $elementConstate[, boolean $verifieAttributs = FALSE, string $message = '']) Rapporte une erreur identifiée par $message si la structure XML de l'élément DOMElement de $elementConstate n'est pas égale à la structure de l'élément DOMElement de $elementAttendu. Exemple 4.22. Utilisation de assertEqualXMLStructure() <?php class StructuresXMLSontEgalesTest extends PHPUnit_Framework_TestCase { public function testEchecAvecDifferentsNomsdeNoeud() { $attendu = new DOMElement('foo'); $constate = new DOMElement('bar'); $this->assertEqualXMLStructure($attendu, $constate); } public function testEchecAvecDifferentsAttributsDeNoeud() { $attendu = new DOMDocument; $attendu->loadXML('<foo bar="true" />'); $constate = new DOMDocument; $constate->loadXML('<foo/>'); $this->assertEqualXMLStructure( $attendu->firstChild, $constate->firstChild, TRUE ); } public function testEchecAvecDecompteDifferentdesNoeudsFils() { $attendu = new DOMDocument; $attendu->loadXML('<foo><bar/><bar/><bar/></foo>'); $constate = new DOMDocument; $constate->loadXML('<foo><bar/></foo>'); $this->assertEqualXMLStructure( $attendu->firstChild, $constate->firstChild ); } public function testEchecAvecDesNoeudsFilsDifferents() { $attendu = new DOMDocument; $attendu->loadXML('<foo><bar/><bar/><bar/></foo>'); $constate = new DOMDocument; $constate->loadXML('<foo><baz/><baz/><baz/></foo>'); Ecrire des tests pour PHPUnit 28 $this->assertEqualXMLStructure( $attendu->firstChild, $constate->firstChild ); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 5.75Mb There were 4 failures: 1) StructuresXMLSontEgalesTest::testEchecAvecDifferentsNomsdeNoeud Failed asserting that two strings are equal. --- Expected +++ Actual -'foo' +'bar' /home/sb/StructuresXMLSontEgalesTest.php:9 2) StructuresXMLSontEgalesTest::testEchecAvecDifferentsAttributsDeNoeud Number of attributes on node "foo" does not match Failed asserting that 0 matches expected 1. /home/sb/StructuresXMLSontEgalesTest.php:22 3) StructuresXMLSontEgalesTest::testEchecAvecDecompteDifferentdesNoeudsFils Number of child nodes of "foo" differs Failed asserting that 1 matches expected 3. /home/sb/StructuresXMLSontEgalesTest.php:35 4) StructuresXMLSontEgalesTest::testEchecAvecDesNoeudsFilsDifferents Failed asserting that two strings are equal. --- Expected +++ Actual -'bar' +'baz' /home/sb/StructuresXMLSontEgalesTest.php:48 FAILURES! Tests: 4, Assertions: 8, Failures: 4.phpunit StructuresXMLSontEgalesTest PHPUnit 3.7.0 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 5.75Mb There were 4 failures: 1) StructuresXMLSontEgalesTest::testEchecAvecDifferentsNomsdeNoeud Failed asserting that two strings are equal. --- Expected +++ Actual -'foo' Ecrire des tests pour PHPUnit 29 +'bar' /home/sb/StructuresXMLSontEgalesTest.php:9 2) StructuresXMLSontEgalesTest::testEchecAvecDifferentsAttributsDeNoeud Number of attributes on node "foo" does not match Failed asserting that 0 matches expected 1. /home/sb/StructuresXMLSontEgalesTest.php:22 3) StructuresXMLSontEgalesTest::testEchecAvecDecompteDifferentdesNoeudsFils Number of child nodes of "foo" differs Failed asserting that 1 matches expected 3. /home/sb/StructuresXMLSontEgalesTest.php:35 4) StructuresXMLSontEgalesTest::testEchecAvecDesNoeudsFilsDifferents Failed asserting that two strings are equal. --- Expected +++ Actual -'bar' +'baz' /home/sb/StructuresXMLSontEgalesTest.php:48 FAILURES! Tests: 4, Assertions: 8, Failures: 4. assertEquals() assertEquals(mixed $attendu, mixed $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si les deux variables $attendu et $constate ne sont pas égales. assertNotEquals() est l'inverse de cette assertion et prend les mêmes paramètres. assertAttributeEquals() et assertAttributeNotEquals() sont des enrobeurs de commodité qui utilisent un attribut public, protected ou private d'une classe ou d'un objet comme valeur constatée. Exemple 4.23. Utilisation de assertEquals() <?php class EqualsTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertEquals(1, 0); } public function testEchec2() { $this->assertEquals('bar', 'baz'); } public function testEchec3() { $this->assertEquals("foo bar baz ", "foo bah baz "); } } Ecrire des tests pour PHPUnit 30 ?> PHPUnit 3.7.0 by Sebastian Bergmann. FFF Time: 0 seconds, Memory: 5.25Mb There were 3 failures: 1) EqualsTest::testEchec Failed asserting that 0 matches expected 1. /home/sb/EqualsTest.php:6 2) EqualsTest::testEchec2 Failed asserting that two strings are equal. --- Expected +++ Actual -'bar' +'baz' /home/sb/EqualsTest.php:11 3) EqualsTest::testEchec3 Failed asserting that two strings are equal. --- Expected +++ Actual 'foo -bar +bah baz ' /home/sb/EqualsTest.php:16 FAILURES! Tests: 3, Assertions: 3, Failures: 3.phpunit EqualsTest PHPUnit 3.7.0 by Sebastian Bergmann. FFF Time: 0 seconds, Memory: 5.25Mb There were 3 failures: 1) EqualsTest::testEchec Failed asserting that 0 matches expected 1. /home/sb/EqualsTest.php:6 2) EqualsTest::testEchec2 Failed asserting that two strings are equal. --- Expected +++ Actual -'bar' +'baz' /home/sb/EqualsTest.php:11 Ecrire des tests pour PHPUnit 31 3) EqualsTest::testEchec3 Failed asserting that two strings are equal. --- Expected +++ Actual 'foo -bar +bah baz ' /home/sb/EqualsTest.php:16 FAILURES! Tests: 3, Assertions: 3, Failures: 3. Des comparaisons plus spécifiques sont utilisées pour des types d'arguments $attendu et $constate plus spécifiques, voir ci-dessous. assertEquals(float $attendu, float $constate[, string $message = '', float $delta = 0]) Rapporte une erreur identifiée par le $message si les deux nombres à virgule flottante $attendu et $constate ne sont pas à moins de $delta l'un de l'autre. Merci de lire comparing floating-point numbers [http://en.wikipedia.org/wiki/ IEEE_754#Comparing_floating-point_numbers] pour comprendre pourquoi $delta est indispensable. Exemple 4.24. Utilisation de assertEquals() avec des nombres à virgule flottante <?php class EqualsTest extends PHPUnit_Framework_TestCase { public function testSucces() { $this->assertEquals(1.0, 1.1, '', 0.2); } public function testEchec() { $this->assertEquals(1.0, 1.1); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. .F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) EqualsTest::testEchec Failed asserting that 1.1 matches expected 1.0. /home/sb/EqualsTest.php:11 FAILURES! Tests: 2, Assertions: 2, Failures: 1.phpunit EqualsTest Ecrire des tests pour PHPUnit 32 PHPUnit 3.7.0 by Sebastian Bergmann. .F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) EqualsTest::testEchec Failed asserting that 1.1 matches expected 1.0. /home/sb/EqualsTest.php:11 FAILURES! Tests: 2, Assertions: 2, Failures: 1. assertEquals(DOMDocument $attendu, DOMDocument $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si la forme canonique non commentée des documents XML représentés par les deux objets DOMDocument objects $attendu et $constate ne sont pas égaux. Exemple 4.25. Utilisation de assertEquals() avec des objets DOMDocument <?php class EqualsTest extends PHPUnit_Framework_TestCase { public function testEchec() { $attendu = new DOMDocument; $attendu->loadXML('<foo><bar/></foo>'); $constate = new DOMDocument; $constate->loadXML('<bar><foo/></bar>'); $this->assertEquals($attendu, $constate); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) EqualsTest::testEchec Failed asserting that two DOM documents are equal. --- Expected +++ Actual <?xml version="1.0"?> -<foo> - <bar/> -</foo> +<bar> + <foo/> +</bar> Ecrire des tests pour PHPUnit 33 /home/sb/EqualsTest.php:12 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit EqualsTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) EqualsTest::testEchec Failed asserting that two DOM documents are equal. --- Expected +++ Actual <?xml version="1.0"?> -<foo> - <bar/> -</foo> +<bar> + <foo/> +</bar> /home/sb/EqualsTest.php:12 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertEquals(object $attendu, object $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si les deux objets $attendu et $constate ne possède pas des valeurs d'attribut égales. Exemple 4.26. Utilisation de assertEquals() avec des objets <?php class EqualsTest extends PHPUnit_Framework_TestCase { public function testEchec() { $attendu = new stdClass; $attendu->foo = 'foo'; $attendu->bar = 'bar'; $constate = new stdClass; $constate->foo = 'bar'; $constate->baz = 'bar'; $this->assertEquals($attendu, $constate); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb Ecrire des tests pour PHPUnit 34 There was 1 failure: 1) EqualsTest::testEchec Failed asserting that two objects are equal. --- Expected +++ Actual stdClass Object ( - 'foo' => 'foo' - 'bar' => 'bar' + 'foo' => 'bar' + 'baz' => 'bar' ) /home/sb/EqualsTest.php:14 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit EqualsTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) EqualsTest::testEchec Failed asserting that two objects are equal. --- Expected +++ Actual stdClass Object ( - 'foo' => 'foo' - 'bar' => 'bar' + 'foo' => 'bar' + 'baz' => 'bar' ) /home/sb/EqualsTest.php:14 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertEquals(array $attendu, array $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si les deux tableaux $attendu et $constate ne sont pas égaux. Exemple 4.27. Utilisation de assertEquals() avec des tableaux <?php class EqualsTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertEquals(array('a', 'b', 'c'), array('a', 'c', 'd')); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. Ecrire des tests pour PHPUnit 35 F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) EqualsTest::testEchec Failed asserting that two arrays are equal. --- Expected +++ Actual Array ( 0 => 'a' - 1 => 'b' - 2 => 'c' + 1 => 'c' + 2 => 'd' ) /home/sb/EqualsTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit EqualsTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) EqualsTest::testEchec Failed asserting that two arrays are equal. --- Expected +++ Actual Array ( 0 => 'a' - 1 => 'b' - 2 => 'c' + 1 => 'c' + 2 => 'd' ) /home/sb/EqualsTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertFalse() assertFalse(bool $condition[, string $message = '']) Rapporte une erreur identifiée par le $message si la $condition est TRUE (VRAIE). Exemple 4.28. Utilisation de assertFalse() <?php class FalseTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertFalse(TRUE); Ecrire des tests pour PHPUnit 36 } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) FalseTest::testEchec Failed asserting that true is false. /home/sb/FalseTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit FalseTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) FalseTest::testEchec Failed asserting that true is false. /home/sb/FalseTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertFileEquals() assertFileEquals(string $attendu, string $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si le fichier identifié par $attendu ne possède pas le même contenu que le fichier identifié par $constate. assertFileNotEquals() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.29. Utilisation de assertFileEquals() <?php class FileEqualsTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertFileEquals('/home/sb/attendu', '/home/sb/constate'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. Ecrire des tests pour PHPUnit 37 F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) FileEqualsTest::testEchec Failed asserting that two strings are equal. --- Expected +++ Actual -'contenu attendu +'contenu constaté ' /home/sb/FileEqualsTest.php:6 FAILURES! Tests: 1, Assertions: 3, Failures: 1.phpunit FileEqualsTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) FileEqualsTest::testEchec Failed asserting that two strings are equal. --- Expected +++ Actual -'contenu attendu +'contenu constaté ' /home/sb/FileEqualsTest.php:6 FAILURES! Tests: 1, Assertions: 3, Failures: 1. assertFileExists() assertFileExists(string $nomfichier[, string $message = '']) Rapporte une erreur identifiée par le $message si le fichier désigné par $nomfichier n'existe pas. assertFileNotExists() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.30. Utilisation de assertFileExists() <?php class FileExistsTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertFileExists('/chemin/vers/fichier'); } } ?> Ecrire des tests pour PHPUnit 38 PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) FileExistsTest::testEchec Failed asserting that file "/chemin/vers/fichier" exists. /home/sb/FileExistsTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit FileExistsTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) FileExistsTest::testEchec Failed asserting that file "/chemin/vers/fichier" exists. /home/sb/FileExistsTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertGreaterThan() assertGreaterThan(mixed $attendu, mixed $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si la valeur de $constate n'est pas supérieure à la valeur de $attendu. assertAttributeGreaterThan() est un enrobeur de commodité qui utilise un attribut public, protected ou private d'une classe ou d'un objet comme valeur constatée. Exemple 4.31. Utilisation de assertGreaterThan() <?php class GreaterThanTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertGreaterThan(2, 1); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: Ecrire des tests pour PHPUnit 39 1) GreaterThanTest::testEchec Failed asserting that 1 is greater than 2. /home/sb/GreaterThanTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit GreaterThanTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) GreaterThanTest::testEchec Failed asserting that 1 is greater than 2. /home/sb/GreaterThanTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertGreaterThanOrEqual() assertGreaterThanOrEqual(mixed $attendu, mixed $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si la valeur de $constate n'est pas supérieure ou égale à la valeur de $attendu. assertAttributeGreaterThanOrEqual() est un enrobeur de commodité qui utilise un attribut public, protected ou private d'une classe ou d'un objet comme valeur constatée. Exemple 4.32. Utilisation de assertGreaterThanOrEqual() <?php class GreatThanOrEqualTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertGreaterThanOrEqual(2, 1); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) GreatThanOrEqualTest::testEchec Failed asserting that 1 is equal to 2 or is greater than 2. /home/sb/GreaterThanOrEqualTest.php:6 FAILURES! Ecrire des tests pour PHPUnit 40 Tests: 1, Assertions: 2, Failures: 1.phpunit GreaterThanOrEqualTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) GreatThanOrEqualTest::testEchec Failed asserting that 1 is equal to 2 or is greater than 2. /home/sb/GreaterThanOrEqualTest.php:6 FAILURES! Tests: 1, Assertions: 2, Failures: 1. assertInstanceOf() assertInstanceOf($attendu, $constate[, $message = '']) Rapporte une erreur identifiée par le $message si $constate n'est pas une instance de $attendu. assertNotInstanceOf() est l'inverse de cette assertion et prend les mêmes arguments. assertAttributeInstanceOf() et assertAttributeNotInstanceOf() sont des enrobeurs de commodité qui peuvent être appliqué à un attribut public, protected ou private d'une classe ou d'un objet. Exemple 4.33. Utilisation de assertInstanceOf() <?php class InstanceOfTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertInstanceOf('RuntimeException', new Exception); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) InstanceOfTest::testEchec Failed asserting that Exception Object (...) is an instance of class "RuntimeException". /home/sb/InstanceOfTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit InstanceOfTest PHPUnit 3.7.0 by Sebastian Bergmann. F Ecrire des tests pour PHPUnit 41 Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) InstanceOfTest::testEchec Failed asserting that Exception Object (...) is an instance of class "RuntimeException". /home/sb/InstanceOfTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertInternalType() assertInternalType($attendu, $constate[, $message = '']) Rapporte une erreur identifiée par le $message si $constate n'est pas du type $attendu. assertNotInternalType() est l'inverse de cette assertion et prend les mêmes arguments. assertAttributeInternalType() et assertAttributeNotInternalType() sont des enrobeurs de commodité qui peuvent être appliqués à un attribut public, protected ou private d'une classe ou d'un objet. Exemple 4.34. Utilisation de assertInternalType() <?php class InternalTypeTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertInternalType('string', 42); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) InternalTypeTest::testEchec Failed asserting that 42 is of type "string". /home/sb/InternalTypeTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit InternalTypeTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) InternalTypeTest::testEchec Ecrire des tests pour PHPUnit 42 Failed asserting that 42 is of type "string". /home/sb/InternalTypeTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertJsonFileEqualsJsonFile() assertJsonFileEqualsJsonFile(mixed $fichierAttendu, mixed $fichierConstate[, string $message = '']) Rapporte une erreur identifiée par $message si la valeur de $fichierConstate correspond à la valeur de $fichierAttendu. Exemple 4.35. Utilisation de assertJsonFileEqualsJsonFile() <?php class JsonFileEqualsJsonFile extends PHPUnit_Framework_TestCase { public function testFailure() { $this->assertJsonFileEqualsJsonFile( 'chemin/vers/fixture/fichier', 'chemin/vers/constate/fichier'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) JsonFileEqualsJsonFile::testFailure Failed asserting that '{"Mascott":"Tux"}' matches JSON string "["Mascott", "Tux", "OS", " /lapistano/JsonFileEqualsJsonFile.php:5 FAILURES! Tests: 1, Assertions: 3, Failures: 1.phpunit JsonFileEqualsJsonFile PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) JsonFileEqualsJsonFile::testFailure Failed asserting that '{"Mascott":"Tux"}' matches JSON string "["Mascott", "Tux", "OS", " /lapistano/JsonFileEqualsJsonFile.php:5 FAILURES! Tests: 1, Assertions: 3, Failures: 1. assertJsonStringEqualsJsonFile() assertJsonStringEqualsJsonFile(mixed $fichierAttendu, mixed $jsonConstate[, string $message = '']) Ecrire des tests pour PHPUnit 43 Rapporte une erreur identifiée par $message si la valeur de $jsonConstate correspond à la valeur de $fichierAttendu. Exemple 4.36. Utilisation de assertJsonStringEqualsJsonFile() <?php class JsonStringEqualsJsonFile extends PHPUnit_Framework_TestCase { public function testFailure() { $this->assertJsonStringEqualsJsonFile( 'chemin/vers/fixture/fichier', json_encode(array("Mascott" => "ux")); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) JsonStringEqualsJsonFile::testFailure Failed asserting that '{"Mascott":"ux"}' matches JSON string "{"Mascott":"Tux"}". /lapistano/JsonStringEqualsJsonFile.php:5 FAILURES! Tests: 1, Assertions: 3, Failures: 1.phpunit JsonStringEqualsJsonFile PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) JsonStringEqualsJsonFile::testFailure Failed asserting that '{"Mascott":"ux"}' matches JSON string "{"Mascott":"Tux"}". /lapistano/JsonStringEqualsJsonFile.php:5 FAILURES! Tests: 1, Assertions: 3, Failures: 1. assertJsonStringEqualsJsonString() assertJsonStringEqualsJsonString(mixed $jsonAttendu, mixed $jsonConstate[, string $message = '']) Rapporte une erreur identifiée par $message si la valeur de $jsonConstate correspond à la valeur de $jsonAttendu. Exemple 4.37. Utilisation de assertJsonStringEqualsJsonString() <?php class JsonStringEqualsJsonString extends PHPUnit_Framework_TestCase { public function testFailure() Ecrire des tests pour PHPUnit 44 { $this->assertJsonStringEqualsJsonString( json_encode(array("Mascott" => "Tux"), json_encode(array("Mascott" => "ux")); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) JsonStringEqualsJsonString::testFailure Failed asserting that two objects are equal. --- Expected +++ Actual stdClass Object ( - 'Mascott' => 'Tux' + 'Mascott' => 'ux' ) /lapistano/JsonStringEqualsJsonString.php:5 FAILURES! Tests: 1, Assertions: 3, Failures: 1.phpunit JsonStringEqualsJsonString PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) JsonStringEqualsJsonString::testFailure Failed asserting that two objects are equal. --- Expected +++ Actual stdClass Object ( - 'Mascott' => 'Tux' + 'Mascott' => 'ux' ) /lapistano/JsonStringEqualsJsonString.php:5 FAILURES! Tests: 1, Assertions: 3, Failures: 1. assertLessThan() assertLessThan(mixed $attendu, mixed $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si la valeur de $constate n'est pas inférieure à la valeur de $attendu. assertAttributeLessThan() est un enrobeur de commodité qui utilise un attribut public, protected ou private d'une classe ou d'un objet comme valeur constatée. Ecrire des tests pour PHPUnit 45 Exemple 4.38. Utilisation de assertLessThan() <?php class LessThanTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertLessThan(1, 2); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) LessThanTest::testEchec Failed asserting that 2 is less than 1. /home/sb/LessThanTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit LessThanTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) LessThanTest::testEchec Failed asserting that 2 is less than 1. /home/sb/LessThanTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertLessThanOrEqual() assertLessThanOrEqual(mixed $attendu, mixed $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si la valeur de $constate n'est pas inférieure ou égale à la valeur de $attendu. assertAttributeLessThanOrEqual() est un enrobeur de commodité qui utilise un attribut public, protected ou private d'une classe ou d'un objet comme valeur attendue. Exemple 4.39. Utilisation de assertLessThanOrEqual() <?php class LessThanOrEqualTest extends PHPUnit_Framework_TestCase { public function testEchec() Ecrire des tests pour PHPUnit 46 { $this->assertLessThanOrEqual(1, 2); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) LessThanOrEqualTest::testEchec Failed asserting that 2 is equal to 1 or is less than 1. /home/sb/LessThanOrEqualTest.php:6 FAILURES! Tests: 1, Assertions: 2, Failures: 1.phpunit LessThanOrEqualTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) LessThanOrEqualTest::testEchec Failed asserting that 2 is equal to 1 or is less than 1. /home/sb/LessThanOrEqualTest.php:6 FAILURES! Tests: 1, Assertions: 2, Failures: 1. assertNull() assertNull(mixed $variable[, string $message = '']) Rapporte une erreur identifiée par le $message si $variable n'est pas NULL. assertNotNull() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.40. Utilisation de assertNull() <?php class NullTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertNull('foo'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. Ecrire des tests pour PHPUnit 47 F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) NullTest::testEchec Failed asserting that 'foo' is null. /home/sb/NotNullTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit NotNullTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) NullTest::testEchec Failed asserting that 'foo' is null. /home/sb/NotNullTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertObjectHasAttribute() assertObjectHasAttribute(string $nomAttribut, object $objet[, string $message = '']) Rapporte une erreur identifiée par le $message si $objet->nomAttribut n'existe pas. assertObjectNotHasAttribute() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.41. Utilisation de assertObjectHasAttribute() <?php class ObjectHasAttributeTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertObjectHasAttribute('foo', new stdClass); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ObjectHasAttributeTest::testEchec Failed asserting that object of class "stdClass" has attribute "foo". Ecrire des tests pour PHPUnit 48 /home/sb/ObjectHasAttributeTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit ObjectHasAttributeTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ObjectHasAttributeTest::testEchec Failed asserting that object of class "stdClass" has attribute "foo". /home/sb/ObjectHasAttributeTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertRegExp() assertRegExp(string $motif, string $chaine[, string $message = '']) Rapporte une erreur identifiée par le $message si $chaine ne correspond pas à l'expression rationnelle $motif. assertNotRegExp() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.42. Utilisation de assertRegExp() <?php class RegExpTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertRegExp('/foo/', 'bar'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) RegExpTest::testEchec Failed asserting that 'bar' matches PCRE pattern "/foo/". /home/sb/RegExpTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit RegExpTest PHPUnit 3.7.0 by Sebastian Bergmann. F Ecrire des tests pour PHPUnit 49 Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) RegExpTest::testEchec Failed asserting that 'bar' matches PCRE pattern "/foo/". /home/sb/RegExpTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertStringMatchesFormat() assertStringMatchesFormat(string $format, string $chaine[, string $message = '']) Rapporte une erreur identifiée par le $message si la chaîne $chaine ne correspond pas à la chaîne de $format. assertStringNotMatchesFormat() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.43. Utilisation de assertStringMatchesFormat() <?php class StringMatchesFormatTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertStringMatchesFormat('%i', 'foo'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) StringMatchesFormatTest::testEchec Failed asserting that 'foo' matches PCRE pattern "/^[+-]?\d+$/s". /home/sb/StringMatchesFormatTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit StringMatchesFormatTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) StringMatchesFormatTest::testEchec Failed asserting that 'foo' matches PCRE pattern "/^[+-]?\d+$/s". Ecrire des tests pour PHPUnit 50 /home/sb/StringMatchesFormatTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. La chaîne de format peut contenir les conteneurs suivants: ? %e: Représente un séparateur de répertoire, par exemple / sur Linux. ? %s: Un ou plusieurs caractères quelconque (y compris des espaces) à l'exception du caractère fin de ligne. ? %S: Zéro ou plusieurs caractères quelconque (y compris des espaces) à l'exception du caractère fin de ligne. ? %a: Un ou plusieurs caractères quelconque (y compris des espaces) y compris les caractères fin de ligne. ? %A: Zéro ou plusieurs caractères quelconque (y compris des espaces) y compris les caractères fin de ligne. ? %w: Zéro ou plusieurs espaces. ? %i: Une valeur entière signée, par exemple +3142, -3142. ? %d: Une valeur entière non signée, par exemple 123456. ? %x: Un ou plusieurs caractères hexadécimaux. C'est-à-dire des caractères dans la plage 0-9, a- f, A-F. ? %f: Un nombre en virgule flottante, par exemple: 3.142, -3.142, 3.142E-10, 3.142e+10. ? %c: Un unique caractère de n'importe quelle sorte. assertStringMatchesFormatFile() assertStringMatchesFormatFile(string $fichierFormat, string $chaine[, string $message = '']) Rapporte une erreur identifiée par le $message si la chaîne $chaine ne correspond pas au contenu de $fichierFormat. assertStringNotMatchesFormatFile() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.44. Utilisation de assertStringMatchesFormatFile() <?php class StringMatchesFormatFileTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertStringMatchesFormatFile('/chemin/vers/attendu.txt', 'foo'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. Ecrire des tests pour PHPUnit 51 F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) StringMatchesFormatFileTest::testEchec Failed asserting that 'foo' matches PCRE pattern "/^[+-]?\d+ $/s". /home/sb/StringMatchesFormatFileTest.php:6 FAILURES! Tests: 1, Assertions: 2, Failures: 1.phpunit StringMatchesFormatFileTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) StringMatchesFormatFileTest::testEchec Failed asserting that 'foo' matches PCRE pattern "/^[+-]?\d+ $/s". /home/sb/StringMatchesFormatFileTest.php:6 FAILURES! Tests: 1, Assertions: 2, Failures: 1. assertSame() assertSame(mixed $attendu, mixed $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si les deux variables $attendu et $constate n'ont pas le même type et la même valeur. assertNotSame() est l'inverse de cette assertion et prend les mêmes arguments. assertAttributeSame() et assertAttributeNotSame() sont des enrobeurs de commodité qui utilisent un attribut public, protected ou private d'une classe ou d'un objet comme valeur constatée. Exemple 4.45. Utilisation de assertSame() <?php class SameTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertSame('2204', 2204); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Ecrire des tests pour PHPUnit 52 Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) SameTest::testEchec Failed asserting that 2204 is identical to '2204'. /home/sb/SameTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit SameTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) SameTest::testEchec Failed asserting that 2204 is identical to '2204'. /home/sb/SameTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertSame(object $attendu, object $constate[, string $message = '']) Rapporte une erreur identifiée par le $message si les deux variables $attendu et $constate ne référence pas le même objet. Exemple 4.46. Utilisation de assertSame() avec des objets <?php class SameTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertSame(new stdClass, new stdClass); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) SameTest::testEchec Failed asserting that two variables reference the same object. /home/sb/SameTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit SameTest PHPUnit 3.7.0 by Sebastian Bergmann. Ecrire des tests pour PHPUnit 53 F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) SameTest::testEchec Failed asserting that two variables reference the same object. /home/sb/SameTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertSelectCount() assertSelectCount(array $selecteur, integer $nombre, mixed $constate[, string $message = '', boolean $isHtml = TRUE]) Rapporte une erreur identifiée par le $message si le sélecteur CSS $selecteur ne correspond pas à $nombre éléments du noeud DOM $constate. $nombre peut avoir l'un des types suivants : ? booléen: présuppose la présence d'éléments correspondant au sélecteur (TRUE) ou l'absence d'éléments (FALSE). ? nombre entier: présuppose le nombre d'éléments. ? tableau: présuppose que le nombre sera dans la plage indiquée en utilisant <, >, <= et >= comme clefs. Exemple 4.47. Utilisation de assertSelectCount() <?php class SelectCountTest extends PHPUnit_Framework_TestCase { protected function setUp() { $this->xml = new DomDocument; $this->xml->loadXML('<foo><bar/><bar/><bar/></foo>'); } public function testAbsenceEchec() { $this->assertSelectCount('foo bar', FALSE, $this->xml); } public function testPresenceEchec() { $this->assertSelectCount('foo baz', TRUE, $this->xml); } public function testCompteExactEchec() { $this->assertSelectCount('foo bar', 5, $this->xml); } public function testPlageEchec() { $this->assertSelectCount('foo bar', array('>'=>6, '<'=>8), $this->xml); Ecrire des tests pour PHPUnit 54 } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 5.50Mb There were 4 failures: 1) SelectCountTest::testAbsenceEchec Failed asserting that true is false. /home/sb/SelectCountTest.php:12 2) SelectCountTest::testPresenceEchec Failed asserting that false is true. /home/sb/SelectCountTest.php:17 3) SelectCountTest::testCompteExactEchec Failed asserting that 3 matches expected 5. /home/sb/SelectCountTest.php:22 4) SelectCountTest::testPlageEchec Failed asserting that false is true. /home/sb/SelectCountTest.php:27 FAILURES! Tests: 4, Assertions: 4, Failures: 4.phpunit SelectCountTest PHPUnit 3.7.0 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 5.50Mb There were 4 failures: 1) SelectCountTest::testAbsenceEchec Failed asserting that true is false. /home/sb/SelectCountTest.php:12 2) SelectCountTest::testPresenceEchec Failed asserting that false is true. /home/sb/SelectCountTest.php:17 3) SelectCountTest::testCompteExactEchec Failed asserting that 3 matches expected 5. /home/sb/SelectCountTest.php:22 4) SelectCountTest::testPlageEchec Failed asserting that false is true. /home/sb/SelectCountTest.php:27 FAILURES! Ecrire des tests pour PHPUnit 55 Tests: 4, Assertions: 4, Failures: 4. assertSelectEquals() assertSelectEquals(array $selecteur, string $contenu, integer $nombre, mixed $constate[, string $message = '', boolean $isHtml = TRUE]) Rapporte une erreur identifiée par le $message si le sélecteur CSS $selecteur ne correspond pas à $nombre éléments dans le noeud DOM $constate possédant la valeur $contenu. $nombre peut avoir l'un des types suivants : ? booléen: présuppose la présence correspondant au sélecteur (TRUE) ou l'absence d'éléments (FALSE). ? nombre entier: présuppose le nombre d'éléments. ? tableau: présuppose que le nombre est dans une plage indiquée en utilisant <, >, <= et >= comme clefs. Exemple 4.48. Utilisation de assertSelectEquals() <?php class SelectEqualsTest extends PHPUnit_Framework_TestCase { protected function setUp() { $this->xml = new DomDocument; $this->xml->loadXML('<foo><bar>Baz</bar><bar>Baz</bar></foo>'); } public function testAbsenceEchec() { $this->assertSelectEquals('foo bar', 'Baz', FALSE, $this->xml); } public function testPresenceEchec() { $this->assertSelectEquals('foo bar', 'Bat', TRUE, $this->xml); } public function testCompteExactEchec() { $this->assertSelectEquals('foo bar', 'Baz', 5, $this->xml); } public function testPlageEchec() { $this->assertSelectEquals('foo bar', 'Baz', array('>'=>6, '<'=>8), $this->xml); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 5.50Mb Ecrire des tests pour PHPUnit 56 There were 4 failures: 1) SelectEqualsTest::testAbsenceEchec Failed asserting that true is false. /home/sb/SelectEqualsTest.php:12 2) SelectEqualsTest::testPresenceEchec Failed asserting that false is true. /home/sb/SelectEqualsTest.php:17 3) SelectEqualsTest::testCompteExactEchec Failed asserting that 2 matches expected 5. /home/sb/SelectEqualsTest.php:22 4) SelectEqualsTest::testPlageEchec Failed asserting that false is true. /home/sb/SelectEqualsTest.php:27 FAILURES! Tests: 4, Assertions: 4, Failures: 4.phpunit SelectEqualsTest PHPUnit 3.7.0 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 5.50Mb There were 4 failures: 1) SelectEqualsTest::testAbsenceEchec Failed asserting that true is false. /home/sb/SelectEqualsTest.php:12 2) SelectEqualsTest::testPresenceEchec Failed asserting that false is true. /home/sb/SelectEqualsTest.php:17 3) SelectEqualsTest::testCompteExactEchec Failed asserting that 2 matches expected 5. /home/sb/SelectEqualsTest.php:22 4) SelectEqualsTest::testPlageEchec Failed asserting that false is true. /home/sb/SelectEqualsTest.php:27 FAILURES! Tests: 4, Assertions: 4, Failures: 4. assertSelectRegExp() assertSelectRegExp(array $selecteur, string $motif, integer $nombre, mixed $constate[, string $message = '', boolean $isHtml = TRUE]) Rapporte une erreur identifiée par le $message si le sélecteur CSS $selecteur ne correspond pas à $nombre éléments dans le noeud DOM $constate possédant une valeur qui correspond au $motif. Ecrire des tests pour PHPUnit 57 $nombre peut avoir l'un des types suivants : ? booléen: présuppose la présence d'éléments correspondant au sélecteur (TRUE) ou l'absence d'éléments (FALSE). ? nombre entier: présuppose le nombre d'éléments. ? tableau: présuppose que le nombre est dans une plage indiquée en utilisant <, >, <= et >= comme clefs. Exemple 4.49. Utilisation de assertSelectRegExp() <?php class SelectRegExpTest extends PHPUnit_Framework_TestCase { protected function setUp() { $this->xml = new DomDocument; $this->xml->loadXML('<foo><bar>Baz</bar><bar>Baz</bar></foo>'); } public function testAbsenceEchec() { $this->assertSelectRegExp('foo bar', '/Ba.*/', FALSE, $this->xml); } public function testPresenceEchec() { $this->assertSelectRegExp('foo bar', '/B[oe]z]/', TRUE, $this->xml); } public function testCompteExactEchec() { $this->assertSelectRegExp('foo bar', '/Ba.*/', 5, $this->xml); } public function testPlageEchec() { $this->assertSelectRegExp('foo bar', '/Ba.*/', array('>'=>6, '<'=>8), $this->xml) } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 5.50Mb There were 4 failures: 1) SelectRegExpTest::testAbsenceEchec Failed asserting that true is false. /home/sb/SelectRegExpTest.php:12 2) SelectRegExpTest::testPresenceEchec Failed asserting that false is true. /home/sb/SelectRegExpTest.php:17 3) SelectRegExpTest::testCompteExactEchec Ecrire des tests pour PHPUnit 58 Failed asserting that 2 matches expected 5. /home/sb/SelectRegExpTest.php:22 4) SelectRegExpTest::testPlageEchec Failed asserting that false is true. /home/sb/SelectRegExpTest.php:27 FAILURES! Tests: 4, Assertions: 4, Failures: 4.phpunit SelectRegExpTest PHPUnit 3.7.0 by Sebastian Bergmann. FFFF Time: 0 seconds, Memory: 5.50Mb There were 4 failures: 1) SelectRegExpTest::testAbsenceEchec Failed asserting that true is false. /home/sb/SelectRegExpTest.php:12 2) SelectRegExpTest::testPresenceEchec Failed asserting that false is true. /home/sb/SelectRegExpTest.php:17 3) SelectRegExpTest::testCompteExactEchec Failed asserting that 2 matches expected 5. /home/sb/SelectRegExpTest.php:22 4) SelectRegExpTest::testPlageEchec Failed asserting that false is true. /home/sb/SelectRegExpTest.php:27 FAILURES! Tests: 4, Assertions: 4, Failures: 4. assertStringEndsWith() assertStringEndsWith(string $suffixe, string $chaine[, string $message = '']) Rapporte une erreur identifiée par le $message si la $chaine ne se termine pas par $suffixe. assertStringEndsNotWith() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.50. Utilisation de assertStringEndsWith() <?php class StringEndsWithTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertStringEndsWith('suffixe', 'foo'); } } ?> Ecrire des tests pour PHPUnit 59 PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 1 second, Memory: 5.00Mb There was 1 failure: 1) StringEndsWithTest::testEchec Failed asserting that 'foo' ends with "suffixe". /home/sb/StringEndsWithTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit StringEndsWithTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 1 second, Memory: 5.00Mb There was 1 failure: 1) StringEndsWithTest::testEchec Failed asserting that 'foo' ends with "suffixe". /home/sb/StringEndsWithTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertStringEqualsFile() assertStringEqualsFile(string $fichierAttendu, string $chaineConstatee[, string $message = '']) Rapporte une erreur identifiée par le $message si le fichier indiqué par $fichierAttendu ne possède pas $chaineConstatee comme contenu. assertStringNotEqualsFile() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.51. Utilisation de assertStringEqualsFile() <?php class StringEqualsFileTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertStringEqualsFile('/home/sb/attendu', 'constate'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: Ecrire des tests pour PHPUnit 60 1) StringEqualsFileTest::testEchec Failed asserting that two strings are equal. --- Expected +++ Actual -'attendu -' +'constate' /home/sb/StringEqualsFileTest.php:6 FAILURES! Tests: 1, Assertions: 2, Failures: 1.phpunit StringEqualsFileTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) StringEqualsFileTest::testEchec Failed asserting that two strings are equal. --- Expected +++ Actual -'attendu -' +'constate' /home/sb/StringEqualsFileTest.php:6 FAILURES! Tests: 1, Assertions: 2, Failures: 1. assertStringStartsWith() assertStringStartsWith(string $prefixe, string $chaine[, string $message = '']) Rapporte une erreur identifiée par le $message si la chaîne $chaine ne commence pas par $prefixe. assertStringStartsNotWith() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.52. Utilisation de assertStringStartsWith() <?php class StringStartsWithTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertStringStartsWith('prefixe', 'foo'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Ecrire des tests pour PHPUnit 61 Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) StringStartsWithTest::testEchec Failed asserting that 'foo' starts with "prefixe". /home/sb/StringStartsWithTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit StringStartsWithTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) StringStartsWithTest::testEchec Failed asserting that 'foo' starts with "prefixe". /home/sb/StringStartsWithTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertTag() assertTag(array $matcheur, string $constate[, string $message = '', boolean $isHtml = TRUE]) Rapporte une erreur identifiée par le $message si $constate n'établit pas de correspondance avec le $matcheur. $matcheur est un tableau associatif qui indique les critères de correspondance pour l'assertion: ? id: le noeud ayant l'attribut donné id doit correspondre à la valeur indiquée. ? tags: le type du noeud doit correspondre à la valeur correspondante. ? attributes: Les attributs du noeud doivent correspondre aux valeurs correspondantes dans le tableau associatif attributes. ? content: le contenu du texte doit correspondre à la valeur donnée. ? parent: le père du noeud doit correspondre au tableau associatif parent. ? child: au moins un des fils directs du noeud doit satisfaire aux critères décrits dans le tableau associatif child. ? ancestor: au moins l'un des ancêtres du noeud doit satisfaire aux critères décrits par le tableau associatif ancestor. ? descendant: au moins l'un des descendants du noeud doit satisfaire les critères décrits dans le tableau associatif descendant. ? children: tableau associatif pour compter les enfants d'un noeud. ? count: le nombre d'enfants correspondants doit être égal à ce nombre. Ecrire des tests pour PHPUnit 62 ? less_than: le nombre d'enfants correspondants doit être inférieur à ce nombre. ? greater_than: le nombre d'enfants correspondants doit être supérieur à ce nombre. ? only: un autre tableau associatif constitué de clefs à utiliser pour faire des correspondances avec les enfants, et seuls les enfants correspondants seront comptabilisés. assertNotTag() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.53. Utilisation de assertTag() <?php // Matcher qui présuppose qu'il existe un élément avec un id="mon_id". $matcher = array('id' => 'mon_id'); // Matcher qui présuppose qu'il existe un tag "span". $matcher = array('tag' => 'span'); // Matcher qui présuppose qu'il existe un tag "span" contenant // "Hello World". $matcher = array('tag' => 'span', 'content' => 'Hello World'); // Matcher qui présuppose qu'il existe un tag "span" dont le contenu correspond au // motif d'expression rationnelle $matcher = array('tag' => 'span', 'content' => '/Essayez P(HP|erl)/'); // Matcher qui présuppose qu'il existe un "span"avec un attribut class class. $matcher = array( 'tag' => 'span', 'attributes' => array('class' => 'list') ); // Matcher qui présuppose qu'il existe un "span" à l'intérieur d'un "div". $matcher = array( 'tag' => 'span', 'parent' => array('tag' => 'div') ); // Matcher qui présuppose qu'il existe un "span" quelque part dans une "table". $matcher = array( 'tag' => 'span', 'ancestor' => array('tag' => 'table') ); // Matcher qui présuppose qu'il existe un "span" avec au moins un fils "em". $matcher = array( 'tag' => 'span', 'child' => array('tag' => 'em') ); // Matcher qui présuppose qu'il existe un "span" contenant un tag "strong" // (éventuellement imbriqué) $matcher = array( 'tag' => 'span', 'descendant' => array('tag' => 'strong') ); // Matcher qui présuppose qu'il existe un "span" contenant de 5 à 10 tags "em" comme // fils directs $matcher = array( 'tag' => 'span', 'children' => array( 'less_than' => 11, Ecrire des tests pour PHPUnit 63 'greater_than' => 4, 'only' => array('tag' => 'em') ) ); // Matcher qui présuppose qu'il existe un "div", avec un ancêtre "ul" et un "li" // parent (avec class="enum"), et contenant un descendant "span" qui contient // un élément avec id="mon_test" et le texte "Hello World". $matcher = array( 'tag' => 'div', 'ancestor' => array('tag' => 'ul'), 'parent' => array( 'tag' => 'li', 'attributes' => array('class' => 'enum') ), 'descendant' => array( 'tag' => 'span', 'child' => array( 'id' => 'mon_test', 'content' => 'Hello World' ) ) ); // Utilise assertTag() pour appliquer un $matcher à un morceau de $html. $this->assertTag($matcher, $html); // Utilise assertTag() pour appliquer un matcher à un morceau de $xml. $this->assertTag($matcher, $xml, '', FALSE); ?> assertThat() Des assertions plus complexes peuvent être formulées en utilisant les classes PHPUnit_Framework_Constraint. Elles peuvent être évaluées en utilisant la méthode assertThat(). Exemple 4.54, « Utilisation de assertThat() » montre comment les contraintes logicalNot() et equalTo() peuvent être utilisées pour exprimer la même assertion que assertNotEquals(). assertThat(mixed $valeur, PHPUnit_Framework_Constraint $contrainte[, $message = '']) Rapporte une erreur identifiée par le $message si la valeur $valeur ne correspond pas à la $contrainte. Exemple 4.54. Utilisation de assertThat() <?php class BiscuitTest extends PHPUnit_Framework_TestCase { public function testEquals() { $leBiscuit = new Biscuit('Ginger'); $monBiscuit = new Biscuit('Ginger'); $this->assertThat( $leBiscuit, $this->logicalNot( $this->equalTo($monBiscuit) ) ); } } Ecrire des tests pour PHPUnit 64 ?> Tableau 4.3, « Contraintes » montre les classes PHPUnit_Framework_Constraint disponibles. Tableau 4.3. Contraintes Contrainte Signification PHPUnit_Framework_Constraint_ Attribute attribute(PHPUnit_ Framework_Constraint $contrainte, $nomAttribut) Contrainte qui applique une autre contrainte à l'attribut d'une classe ou d'un objet. PHPUnit_Framework_Constraint_ IsAnything anything() Contrainte qui accepte n'importe quelle valeur en entrée. PHPUnit_Framework_Constraint_ ArrayHasKey arrayHasKey(mixed $clef) Contrainte qui présuppose que le tableau pour lequel elle est évaluée possède une clef donnée.. PHPUnit_Framework_Constraint_ TraversableContains contains(mixed $valeur) Contrainte qui présuppose que le tableau ou l'objet qui implémente l'interface Iterator pour lequel elle est évaluée contient une valeur donnée.. PHPUnit_Framework_Constraint_ IsEqual equalTo($valeur, $delta = 0, $profondeurMax = 10) Contrainte qui vérifie si une valeur est égale à une autre. PHPUnit_Framework_ Constraint_Attribute attributeEqualTo($nomAttribut, $valeur, $delta = 0, $profondeurMax = 10) Contrainte qui vérifie si une valeur est égale à l'attribut d'une classe ou d'un objet. PHPUnit_Framework_Constraint_ FileExists fileExists() Contrainte qui vérifie si le (nom de) fichier pour lequel elle est évaluée existe. PHPUnit_Framework_Constraint_ GreaterThan greaterThan(mixed $valeur) Contrainte qui présuppose que la valeur pour laquelle elle est évaluée est supérieure à une valeur donnée. PHPUnit_Framework_Constraint_ Or greaterThanOrEqual(mixed $valeur) Contrainte qui présuppose que la valeur pour laquelle elle est évaluée et supérieure ou égale à une valeur donnée. PHPUnit_Framework_ Constraint_ClassHasAttribute classHasAttribute(string $nomAttribut) Contrainte qui présuppose que la classe pour laquelle elle est évaluée possède un attribut donné. PHPUnit_Framework_Constraint_ ClassHasStaticAttribute classHasStaticAttribute(string $nomAttribut) Contrainte qui présuppose que la classe pour laquelle elle est évaluée possède un attribut statique donné. PHPUnit_Framework_ Constraint_ObjectHasAttribute hasAttribute(string $nomAttribut) Contrainte qui présuppose que l'objet pour lequel elle est évaluée possède un attribut donné. PHPUnit_Framework_Constraint_ IsIdentical identicalTo(mixed $valeur) Contrainte qui présuppose qu'une valeur est identique à une autre. PHPUnit_Framework_Constraint_ IsFalse isFalse() Contrainte qui présuppose que la valeur pour laquelle elle est évaluée est FALSE. Ecrire des tests pour PHPUnit 65 Contrainte Signification PHPUnit_Framework_Constraint_ IsInstanceOf isInstanceOf(string $nomClasse) Contrainte qui présuppose que l'objet pour lequel elle est évaluée est une instance d'une classe donnée. PHPUnit_Framework_Constraint_ IsNull isNull() Contrainte qui présuppose que la valeur pour laquelle elle est évaluée est NULL. PHPUnit_Framework_Constraint_ IsTrue isTrue() Contrainte qui présuppose que la valeur pour laquelle elle est évaluée est TRUE. PHPUnit_Framework_Constraint_ IsType isType(string $type) Contrainte qui présuppose que la valeur pour laquelle elle est évaluée est du type indiqué. PHPUnit_Framework_Constraint_ LessThan lessThan(mixed $valeur) Contrainte qui présuppose que la valeur pour laquelle elle est évaluée est inférieure à la valeur donnée. PHPUnit_Framework_Constraint_Or lessThanOrEqual(mixed $valeur) Contrainte qui présuppose que la valeur pour laquelle elle est évaluée est inférieure ou égale à une valeur donnée. logicalAnd() ET logique (AND). logicalNot(PHPUnit_Framework_ Constraint $contrainte) NON logique (NOT). logicalOr() OU logique (OU). logicalXor() OU exclusif logique (XOR). PHPUnit_Framework_ Constraint_PCREMatch matchesRegularExpression(string $motif) Contrainte qui présuppose que la chaîne pour laquelle elle est évaluée correspond à une expression rationnelle. PHPUnit_Framework_ Constraint_StringContains stringContains(string $chaine, bool $casse) Contrainte qui présuppose que la chaîne pour laquelle elle est évaluée contient une chaîne donnée. PHPUnit_Framework_ Constraint_StringEndsWith stringEndsWith(string $suffixe) Contrainte qui présuppose que la chaîne pour laquelle elle est évaluée se termine avec un suffixe donné. PHPUnit_Framework_ Constraint_StringStartsWith stringStartsWith(string $prefixe) Contrainte qui présuppose que la chaîne pour laquelle elle est évaluée commence par un préfixe donné. assertTrue() assertTrue(bool $condition[, string $message = '']) Rapporte une erreur identifiée par le $message si la $condition est FALSE. Exemple 4.55. Utilisation de assertTrue() <?php class TrueTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertTrue(FALSE); } } Ecrire des tests pour PHPUnit 66 ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) TrueTest::testEchec Failed asserting that false is true. /home/sb/TrueTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit TrueTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) TrueTest::testEchec Failed asserting that false is true. /home/sb/TrueTest.php:6 FAILURES! Tests: 1, Assertions: 1, Failures: 1. assertXmlFileEqualsXmlFile() assertXmlFileEqualsXmlFile(string $fichierAttendu, string $fichierConstate[, string $message = '']) Rapporte une erreur identifiée par le $message si le document XML dans $fichierConstate n'est pas égal au document XML dans $fichierAttendu. assertXmlFileNotEqualsXmlFile() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.56. Utilisation de assertXmlFileEqualsXmlFile() <?php class XmlFileEqualsXmlFileTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertXmlFileEqualsXmlFile( '/home/sb/attendu.xml', '/home/sb/constate.xml'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Ecrire des tests pour PHPUnit 67 Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) XmlFileEqualsXmlFileTest::testEchec Failed asserting that two DOM documents are equal. --- Expected +++ Actual <?xml version="1.0"?> <foo> - <bar/> + <baz/> </foo> /home/sb/XmlFileEqualsXmlFileTest.php:7 FAILURES! Tests: 1, Assertions: 3, Failures: 1.phpunit XmlFileEqualsXmlFileTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) XmlFileEqualsXmlFileTest::testEchec Failed asserting that two DOM documents are equal. --- Expected +++ Actual <?xml version="1.0"?> <foo> - <bar/> + <baz/> </foo> /home/sb/XmlFileEqualsXmlFileTest.php:7 FAILURES! Tests: 1, Assertions: 3, Failures: 1. assertXmlStringEqualsXmlFile() assertXmlStringEqualsXmlFile(string $fichierAttendu, string $xmlConstate[, string $message = '']) Rapporte une erreur identifiée par le $message si le document XML dans $xmlConstate n'est pas égal au document XML dans $fichierAttendu. assertXmlStringNotEqualsXmlFile() est l'inverse de cette assertion et prend les mêmes arguments. Exemple 4.57. Utilisation de assertXmlStringEqualsXmlFile() <?php class XmlStringEqualsXmlFileTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertXmlStringEqualsXmlFile( Ecrire des tests pour PHPUnit 68 '/home/sb/attendu.xml', '<foo><baz/></foo>'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) XmlStringEqualsXmlFileTest::testEchec Failed asserting that two DOM documents are equal. --- Expected +++ Actual <?xml version="1.0"?> <foo> - <bar/> + <baz/> </foo> /home/sb/XmlStringEqualsXmlFileTest.php:7 FAILURES! Tests: 1, Assertions: 2, Failures: 1.phpunit XmlStringEqualsXmlFileTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) XmlStringEqualsXmlFileTest::testEchec Failed asserting that two DOM documents are equal. --- Expected +++ Actual <?xml version="1.0"?> <foo> - <bar/> + <baz/> </foo> /home/sb/XmlStringEqualsXmlFileTest.php:7 FAILURES! Tests: 1, Assertions: 2, Failures: 1. assertXmlStringEqualsXmlString() assertXmlStringEqualsXmlString(string $xmlAttendu, string $xmlConstateXml[, string $message = '']) Rapporte une erreur identifiée par le $message si le document XML dans $xmlConstate n'est pas égal au document XML dans $xmlAttendu. assertXmlStringNotEqualsXmlString() est l'inverse de cette assertion et prend les mêmes arguments. Ecrire des tests pour PHPUnit 69 Exemple 4.58. Utilisation de assertXmlStringEqualsXmlString() <?php class XmlStringEqualsXmlStringTest extends PHPUnit_Framework_TestCase { public function testEchec() { $this->assertXmlStringEqualsXmlString( '<foo><bar/></foo>', '<foo><baz/></foo>'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) XmlStringEqualsXmlStringTest::testEchec Failed asserting that two DOM documents are equal. --- Expected +++ Actual <?xml version="1.0"?> <foo> - <bar/> + <baz/> </foo> /home/sb/XmlStringEqualsXmlStringTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit XmlStringEqualsXmlStringTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 0 seconds, Memory: 5.00Mb There was 1 failure: 1) XmlStringEqualsXmlStringTest::testEchec Failed asserting that two DOM documents are equal. --- Expected +++ Actual <?xml version="1.0"?> <foo> - <bar/> + <baz/> </foo> /home/sb/XmlStringEqualsXmlStringTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1. 70 Chapitre 5. Le lanceur de tests en ligne de commandes Le lanceur de tests en ligne de commandes de PHPUnit peut être appelé via la commande phpunit. Le code suivant montre comment exécuter des tests avec le lanceur de tests en ligne de commandes de PHPUnit: PHPUnit 3.7.0 by Sebastian Bergmann. .. Time: 0 seconds OK (2 tests, 2 assertions)phpunit ArrayTest PHPUnit 3.7.0 by Sebastian Bergmann. .. Time: 0 seconds OK (2 tests, 2 assertions) Pour chaque test exécuté, l'outil en ligne de commandes de PHPUnit affiche un caractère pour indiquer l'avancement: . Affiché quand le test a réussi. F Affiché quand une assertion échoue lors de l'exécution d'une méthode de test. E Affiché quand une erreur survient pendant l'exécution d'une méthode de test. S Affiché quand le test a été sauté (voir Chapitre 9, Tests incomplets et sautés). I Affiché quand le test est marqué comme incomplet ou pas encore implémenté (voir Chapitre 9, Tests incomplets et sautés). PHPUnit différencie les échecs et les erreurs. Un échec est une assertion PHPUnit violée comme un appel en échec de assertEquals(). Une erreur est une exception inattendue ou une erreur PHP. Parfois cette distinction s'avère utile car les erreurs tendent à être plus faciles à corriger que les échecs. Si vous avez une longue liste de problèmes, il vaut mieux éradiquer d'abord les erreurs pour voir s'il reste encore des échecs uen fois qu'elles ont été corrigées. Options de la ligne de commandes Jetons un oeil aux options du lanceur de tests en ligne de commandes dans le code suivant : PHPUnit 3.7.0 by Sebastian Bergmann. Usage: phpunit [switches] UnitTest [UnitTest.php] phpunit [switches] <directory> --log-junit <file> Log test execution in JUnit XML format to file. --log-tap <file> Log test execution in TAP format to file. --log-json <file> Log test execution in JSON format. --coverage-clover <file> Generate code coverage report in Clover XML format. --coverage-html <dir> Generate code coverage report in HTML format. Le lanceur de tests en ligne de commandes 71 --coverage-php <file> Serialize PHP_CodeCoverage object to file. --coverage-text=<file> Generate code coverage report in text format. Default to writing to the standard output. --testdox-html <file> Write agile documentation in HTML format to file. --testdox-text <file> Write agile documentation in Text format to file. --filter <pattern> Filter which tests to run. --group ... Only runs tests from the specified group(s). --exclude-group ... Exclude tests from the specified group(s). --list-groups List available test groups. --loader <loader> TestSuiteLoader implementation to use. --printer <printer> TestSuiteListener implementation to use. --repeat <times> Runs the test(s) repeatedly. --tap Report test execution progress in TAP format. --testdox Report test execution progress in TestDox format. --colors Use colors in output. --stderr Write to STDERR instead of STDOUT. --stop-on-error Stop execution upon first error. --stop-on-failure Stop execution upon first error or failure. --stop-on-skipped Stop execution upon first skipped test. --stop-on-incomplete Stop execution upon first incomplete test. --strict Run tests in strict mode. -v|--verbose Output more verbose information. --debug Display debbuging information during test execution. --process-isolation Run each test in a separate PHP process. --no-globals-backup Do not backup and restore $GLOBALS for each test. --static-backup Backup and restore static attributes for each test. --bootstrap <file> A "bootstrap" PHP file that is run before the tests. -c|--configuration <file> Read configuration from XML file. --no-configuration Ignore default configuration file (phpunit.xml). --include-path <path(s)> Prepend PHP's include_path with given path(s). -d key[=value] Sets a php.ini value. -h|--help Prints this usage information. --version Prints the version and exits. --debug Output debugging information.phpunit --help PHPUnit 3.7.0 by Sebastian Bergmann. Usage: phpunit [switches] UnitTest [UnitTest.php] phpunit [switches] <directory> --log-junit <file> Log test execution in JUnit XML format to file. --log-tap <file> Log test execution in TAP format to file. --log-json <file> Log test execution in JSON format. --coverage-clover <file> Generate code coverage report in Clover XML format. --coverage-html <dir> Generate code coverage report in HTML format. --coverage-php <file> Serialize PHP_CodeCoverage object to file. --coverage-text=<file> Generate code coverage report in text format. Default to writing to the standard output. --testdox-html <file> Write agile documentation in HTML format to file. --testdox-text <file> Write agile documentation in Text format to file. --filter <pattern> Filter which tests to run. --group ... Only runs tests from the specified group(s). --exclude-group ... Exclude tests from the specified group(s). Le lanceur de tests en ligne de commandes 72 --list-groups List available test groups. --loader <loader> TestSuiteLoader implementation to use. --printer <printer> TestSuiteListener implementation to use. --repeat <times> Runs the test(s) repeatedly. --tap Report test execution progress in TAP format. --testdox Report test execution progress in TestDox format. --colors Use colors in output. --stderr Write to STDERR instead of STDOUT. --stop-on-error Stop execution upon first error. --stop-on-failure Stop execution upon first error or failure. --stop-on-skipped Stop execution upon first skipped test. --stop-on-incomplete Stop execution upon first incomplete test. --strict Run tests in strict mode. -v|--verbose Output more verbose information. --debug Display debbuging information during test execution. --process-isolation Run each test in a separate PHP process. --no-globals-backup Do not backup and restore $GLOBALS for each test. --static-backup Backup and restore static attributes for each test. --bootstrap <file> A "bootstrap" PHP file that is run before the tests. -c|--configuration <file> Read configuration from XML file. --no-configuration Ignore default configuration file (phpunit.xml). --include-path <path(s)> Prepend PHP's include_path with given path(s). -d key[=value] Sets a php.ini value. -h|--help Prints this usage information. --version Prints the version and exits. --debug Output debugging information. phpunit UnitTest Exécute les tests qui sont fournis par la classe UnitTest. Cette classe est supposée être déclarée dans le fichier source UnitTest.php. UnitTest doit soit être une classe qui hérite de PHPUnit_Framework_TestCase soit une classe qui fournit une méthode public static suite() retournant un objet PHPUnit_Framework_Test, par exemple une instance de la classe PHPUnit_Framework_TestSuite. phpunit UnitTest UnitTest.php Exécute les tests qui sont fournis par la classe UnitTest. Cette classe est supposée être déclarée dans le fichier source indiqué. --log-junit Génère un fichier de log au format JUnit XML pour les tests exécutés. Voir Chapitre 18, Journalisation pour plus de détails. --log-tap Génère un fichier de log utilisant le format Test Anything Protocol (Protocol de test universel ou TAP) [http:// testanything.org/] pour les tests exécutés. Voir Chapitre 18, Journalisation pour plus de détails. --log-json Génère un fichier de log en utilisant le format JSON [http:// www.json.org/]. Voir Chapitre 18, Journalisation pour plus de détails. --coverage-html Génère un rapport de couverture de code au format HTML. Voir Chapitre 14, Analyse de couverture de code pour plus de détails. Le lanceur de tests en ligne de commandes 73 Merci de noter que cette fonctionnalité n'est seulement disponible que lorsque les extensions tokenizer et Xdebug sont installées. --coverage-clover Génère un fichier de log au format XML avec les informations de couverture de code pour les tests exécutés. Voir Chapitre 18, Journalisation pour plus de détails. Merci de noter que cette fonctionnalité n'est seulement disponible que lorsque les extensions tokenizer et Xdebug sont installées. --coverage-php Génère un objet sérialisé PHP_CodeCoverage contenant les informations de couverture de code. Merci de noter que cette fonctionnalité n'est seulement disponible que lorsque les extensions tokenizer et Xdebug sont installées. --coverage-text Génère un fichier de log ou une sortie écran sur la ligne de commandes en format humainement lisible avec les informations de couverture de code pour les tests exécutés. Voir Chapitre 18, Journalisation pour plus de détails. Merci de noter que cette fonctionnalité n'est seulement disponible que lorsque les extensions tokenizer et Xdebug sont installées. --testdox-html et -- testdox-text Génère la documentation agile au format HTML ou texte pur pour les tests exécutés. Voir Chapitre 15, Autres utilisations des tests pour plus de détails. --filter Exécute seulement les tests dont le nom correspond au motif donné. Le motif peut être soit le nom d'un test particulier, soit une expression rationnelle [http://www.php.net/pcre] qui correspond à plusieurs noms de tests. --group Exécute seulement les tests appartenant à un/des groupe(s) indiqué(s). Un test peut être signalé comme appartenant à un groupe en utilisant l'annotation group. L'annotation author est un alias pour group permettant de filtrer les tests en se basant sur leurs auteurs. --exclude-group Exclut les tests d'un/des groupe(s) indiqué(s). Un test peut être signalé comme appartenant à un groupe en utilisant l'annotation group. --list-groups Liste les groupes de tests disponibles. --loader Indique l'implémentation de PHPUnit_Runner_TestSuiteLoader à utiliser. Le chargeur standard de suite de tests va chercher les fichiers source dans le répertoire de travail actuel et dans chaque répertoire qui est indiqué dans la directive de configuration PHP include_path. Suivant les conventions de nommage PEAR, le nom d'une classe tel que Projet_Paquetage_Classe est calqué sur le nom de fichier source Projet/Paquetage/Classe.php. Le lanceur de tests en ligne de commandes 74 --printer Indique l'afficheur de résultats à utiliser. Cette classe d'afficheur doit hériter de PHPUnit_Util_Printer et implémenter l'interface PHPUnit_Framework_TestListener. --repeat Répéter l'exécution du(des) test(s) le nombre indiqué de fois. --tap Rapporte l'avancement des tests en utilisant le Test Anything Protocol (TAP) [http://testanything.org/]. Voir Chapitre 18, Journalisation pour plus de détails. --testdox Rapporte l'avancement des tests sous forme de documentation agile. Voir Chapitre 15, Autres utilisations des tests pour plus de détails. --colors Utilise des couleurs pour l'affichage. --stderr Utilise optionnellement STDERR au lieu de STDOUT pour l'affichage. --stop-on-error Arrête l'exécution à la première erreur. --stop-on-failure Arrête l'exécution à la première erreur ou au premier échec. --stop-on-skipped Arrête l'exécution au premier test sauté. --stop-on-incomplete Arrête l'exécution au premier test incomplet. --strict Exécute les tests en mode strict. --verbose Affiche des informations plus détaillées, par exemple le nom des tests qui sont incomplets ou qui ont été sautés. --process-isolation Exécute chaque test dans un processus PHP distinct. --no-globals-backup Ne pas sauvegarder et restaurer $GLOBALS. Voir la section intitulée « Etat global » pour plus de détails. --static-backup Sauvegarde et restaure les attributs statiques des classes définies par l'utilisateur. Voir la section intitulée « Etat global » pour plus de détails. --bootstrap Un fichier PHP "amorce" ("bootstrap") est exécuté avant les tests. --configuration, -c Lit la configuration dans un fichier XML. Voir Annexe C, Le fichier de configuration Configuration pour plus de détails. Si phpunit.xml ou phpunit.xml.dist (dans cet ordre) existent dans le répertoire de travail actuel et que -- configuration n'est pas utilisé, la configuration sera automatiquement lue dans ce fichier. --no-configuration Ignorer phpunit.xml et phpunit.xml.dist du répertoire de travail actuel. --include-path Préfixe l'include_path PHP avec le(s) chemin(s) donné(s). -d Fixe la valeur des options de configuration PHP données. --debug Affiche des informations de débogage telles que le nom d'un test quand son exécution démarre. 75 Chapitre 6. Fixtures 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 4.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 4.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 6.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 6.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)); } Fixtures 76 } ?> 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 6.2. Exemple montrant toutes les méthodes canevas disponibles <?php class TemplateMethodsTest extends PHPUnit_Framework_TestCase { public static function setUpBeforeClass() { fwrite(STDOUT, __METHOD__ . " "); } protected function setUp() { fwrite(STDOUT, __METHOD__ . " "); } protected function assertPreConditions() { fwrite(STDOUT, __METHOD__ . " "); } public function testOne() { fwrite(STDOUT, __METHOD__ . " "); $this->assertTrue(TRUE); } public function testTwo() { fwrite(STDOUT, __METHOD__ . " "); $this->assertTrue(FALSE); } protected function assertPostConditions() { fwrite(STDOUT, __METHOD__ . " "); } protected function tearDown() { fwrite(STDOUT, __METHOD__ . " "); } public static function tearDownAfterClass() { fwrite(STDOUT, __METHOD__ . " "); } protected function onNotSuccessfulTest(Exception $e) { fwrite(STDOUT, __METHOD__ . " "); throw $e; Fixtures 77 } } ?> PHPUnit 3.7.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.phpunit TemplateMethodsTest PHPUnit 3.7.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. Plus de setUp() que de tearDown() 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 Fixtures 78 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. Variantes 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. Partager les Fixtures 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 6.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 6.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 10, 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. Etat global Il est difficile de tester du code qui utilise des singletons. [http://googletesting.blogspot.com/2008/05/ tott-using-dependancy-injection-to.html] La même chose est vraie pour le code qui utilise des variables Fixtures 79 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. Note 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'); // ... } Note 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 { Fixtures 80 protected $backupStaticAttributesBlacklist = array( 'className' => array('attributeName') ); // ... } Note 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. 81 Chapitre 7. Organiser les tests L'un des objectifs de PHPUnit (voir Chapitre 2, Objectifs de PHPUnit) est que les tests soient combinables: nous voulons pouvoir exécuter n'importe quel nombre ou combinaison de tests ensembles, par exemple tous les tests pour le projet entier, ou les tests pour toutes les classes d'un composant qui constitue une partie du projet ou simplement les tests d'une seule classe particulière. PHPUnit gère différente façon d'organiser les tests et de les combiner en une suite de tests. Ce chapitre montre les approches les plus communément utilisées. Composer une suite de tests en utilisant le système de fichiers La façon probablement la plus simple de composer une suite de tests est de conserver tous les fichiers sources des cas de test dans un répertoire test. PHPUnit peut automatiquement trouver et exécuter les tests en parcourant récursivement le répertoire test. Jetons un oeil à la suite de tests de la bibliothèque Object_Freezer [http://github.com/ sebastianbergmann/php-object-freezer/]. En regardant la structure des répertoires du projet, nous voyons que les classes de cas de test dans le répertoire Tests reflètent la structure des paquetages et des classes du système en cours de test (SCT, System Under Test ou SUT) dans le répertoire Object: Object Tests |-- Freezer |-- Freezer | |-- HashGenerator | |-- HashGenerator | | `-- NonRecursiveSHA1.php | | `-- NonRecursiveSHA1Test.php | |-- HashGenerator.php | | | |-- IdGenerator | |-- IdGenerator | | `-- UUID.php | | `-- UUIDTest.php | |-- IdGenerator.php | | | |-- LazyProxy.php | | | |-- Storage | |-- Storage | | `-- CouchDB.php | | `-- CouchDB | | | | |-- WithLazyLoadTest.php | | | | `-- WithoutLazyLoadTest.php | |-- Storage.php | |-- StorageTest.php | `-- Util.php | `-- UtilTest.php `-- Freezer.php `-- FreezerTest.php Pour exécuter tous les tests de la bibliothèque, nous n'avons qu'à faire pointer le lanceur de tests en ligne de commandes de PHPUnit sur ce répertoire test : PHPUnit 3.7.0 by Sebastian Bergmann. ............................................................ 60 / 75 ............... Time: 0 seconds OK (75 tests, 164 assertions)phpunit Tests PHPUnit 3.7.0 by Sebastian Bergmann. ............................................................ 60 / 75 ............... Time: 0 seconds Organiser les tests 82 OK (75 tests, 164 assertions) Note Si vous pointez le lanceur de tests en ligne de commandes de PHPUnit sur un répertoire, il va chercher les fichiers *Test.php. Pour n'exécuter que les tests déclarés dans la classe de cas de test Object_FreezerTest dans Tests/FreezerTest.php, nous pouvons utiliser la commande suivante : PHPUnit 3.7.0 by Sebastian Bergmann. ............................ Time: 0 seconds OK (28 tests, 60 assertions)phpunit Tests/FreezerTest PHPUnit 3.7.0 by Sebastian Bergmann. ............................ Time: 0 seconds OK (28 tests, 60 assertions) Pour un contrôle plus fin sur les tests à exécuter, nous pouvons utiliser l'option --filter: PHPUnit 3.7.0 by Sebastian Bergmann. . Time: 0 seconds OK (1 test, 2 assertions)phpunit --filter testFreezingAnObjectWorks Tests PHPUnit 3.7.0 by Sebastian Bergmann. . Time: 0 seconds OK (1 test, 2 assertions) Note Un inconvénient de cette approche est que nous n'avons pas de contrôle sur l'ordre dans lequel les tests sont exécutés. Ceci peut conduire à des problèmes concernant les dépendances des tests, voir la section intitulée « Dépendances des tests ». Dans la prochaine section, nous verrons comment nous pouvons rendre l'ordre d'exécution des tests explicité en utilisant le fichier de configuration XML. Composer une suite de tests en utilisant la configuration XML Le fichier de configuration XML de PHPUnit (Annexe C, Le fichier de configuration Configuration) peut aussi être utilisé pour composer une suite de tests. Exemple 7.1, « Composer une suite de tests en utilisant la configuration XML » montre un exemple minimaliste qui va ajouter toutes les classes *Test trouvées dans les fichiers *Test.php quand Tests est parcouru récursivement. Organiser les tests 83 Exemple 7.1. Composer une suite de tests en utilisant la configuration XML <phpunit> <testsuites> <testsuite name="Object_Freezer"> <directory>Tests</directory> </testsuite> </testsuites> </phpunit> L'ordre dans lequel les tests sont exécutés peut être rendu explicite : Exemple 7.2. Composer une suite de tests en utilisant la configuration XML <phpunit> <testsuites> <testsuite name="Object_Freezer"> <file>Tests/Freezer/HashGenerator/NonRecursiveSHA1Test.php</file> <file>Tests/Freezer/IdGenerator/UUIDTest.php</file> <file>Tests/Freezer/UtilTest.php</file> <file>Tests/FreezerTest.php</file> <file>Tests/Freezer/StorageTest.php</file> <file>Tests/Freezer/Storage/CouchDB/WithLazyLoadTest.php</file> <file>Tests/Freezer/Storage/CouchDB/WithoutLazyLoadTest.php</file> </testsuite> </testsuites> </phpunit> 84 Chapitre 8. Tester des bases de données De nombreux exemples de tests unitaires de niveau débutant ou intermédiaire dans de nombreux langages de programmation suggèrent qu'il est parfaitement facile de tester la logique de votre application avec de simples tests. Pour les applications centrées sur une base de données, c'est loin d'être la réalité. Commencez à utiliser WordPress, TYPO3 ou Symfony avec Doctrine ou Propel, par exemple, et nous serez vite confrontés à des problèmes considérables avec PHPUnit : juste parce que la base de données est vraiment étroitement liée à ces bibliothèques. Vous connaissez probablement ce scénario rencontré tous les jours sur les projets, dans lequel vous voulez mettre à l'oeuvre votre savoir-faire tout neuf ou déjà aguerri en matière de PHPUnit et où vous vous retrouvez bloqué par l'un des problèmes suivants : 1. La méthode que vous voulez tester exécute une opération de jointure plutôt vaste et utilise les données pour calculer certains résultats importants. 2. Votre logique métier exécute un mélange d'instructions SELECT, INSERT, UPDATE et DELETE. 3. Vous devez configurer les données de test dans (éventuellement beaucoup) plus de deux tables pour obtenir des données initiales raisonnables pour les méthodes que vous voulez tester. L'extension DbUnit simplifie considérablement la configuration d'une base de données à des fins de test et vous permet de vérifier le contenu d'une base de données après avoir réalisé une suite d'opérations. Elle peut être installée comme ceci : pear install phpunit/DbUnit Systèmes gérés pour tester des bases de données DbUnit gère actuellement MySQL, PostgreSQL, Oracle et SQLite. Via l'intégration de Zend Framework [http://framework.zend.com] ou de Doctrine 2 [http://www.doctrine-project.org] il est possible d'accéder à d'autres systèmes de base de données comme IBM DB2 ou Microsoft SQL Server. Difficultés pour tester les bases de données Il y a une bonne raison pour laquelle les exemples concernant le test unitaire n'inclut pas d'interaction avec une base de données : ces types de test sont à la fois complexes à configurer et à maintenir. Quand vous faites des tests sur votre base de données, vous devez prendre soin des variables suivantes : ? Le schéma et les tables de la base de données ? Insérer les lignes nécessaires pour le test dans ces tables ? Vérifier l'état de la base de données après que votre test a été exécuté ? Nettoyer la base de données pour chaque nouveau test Comme de nombreuses APIs de base de données comme PDO, MySQLi ou OCI8 sont lourdes à utiliser et verbeuses à écrire, réaliser ces étapes à la main est un cauchemar absolu. Le code de test doit être aussi court et précis que possible pour plusieurs raisons : ? Vous ne voulez pas modifier un volume considérable de code de test pour de petites modifications dans votre code de production. Tester des bases de données 85 ? Vous voulez être capable de lire et de comprendre le code de test facilement, même des mois après l'avoir écrit. De plus, vous devez prendre conscience que la base de données est essentiellement une variable globale en entrée pour votre code. Deux tests de votre série de tests peuvent être exécutés sur la même base de données, potentiellement en réutilisant les données plusieurs fois. Un échec dans un test peut facilement affecter le résultat des tests suivants rendant votre expérimentation de test très difficile. L'étape de nettoyage mentionnée précédemment est d'une importance majeure pour résoudre le problème posé par le fait que « la base de données est une variable globale en entrée ». DbUnit aide à simplifier tous ces problèmes avec le test de base de données d'une manière élégante. Là où PHPUnit ne peut pas vous aider c'est pour le fait que les tests de base de données sont très lents comparés aux tests n'en utilisant pas. Selon l'importance des interactions avec votre base de données, vos tests peuvent s'exécuter sur une durée considérable. Cependant, si vous gardez petit le volume de données utilisées pour chaque test et que vous essayez de tester le plus de code possible en utilisant des tests qui ne font pas appel à une base de données, vous pouvez facilement rester très en dessous d'une minute, même pour de grandes séries de tests. La suite de test du projet Doctrine 2 [http://www.doctrine-project.org], par exemple, possède actuellement une suite de tests d'environ 1000 tests dont presque la moitié accède à la base de données et continue à s'exécuter en 15 secondes sur une base de données MySQL sur un ordinateur de bureau standard. Les quatre phases d'un test de base de données Dans son livre sur les canevas de tests xUnit, Gerard Meszaros liste les quatre phases d'un test unitaire : 1. Configurer une fixture 2. Expérimenter le système à tester 3. Vérifier les résultats 4. Nettoyer Qu'est-ce qu'une fixture ? Une fixture décrit l'état initial dans lequel se trouvent votre application et votre base de données quand vous exécutez un test. Tester la base de données nécessite au moins d'intervenir dans setup et teardown pour nettoyer et écrire les données de fixture nécessaires dans vos tables. Cependant, l'extension de base de données possède une bonne raison de ramener les quatre phases dans un test de base de données pour constituer le processus suivant qui est exécuté pour chacun des tests : 1. Nettoyer la base de données Puisqu'il y a toujours un premier test qui s'exécute en faisant appel à la base de données, vous n'êtes pas sûr qu'il y ait déjà des données dans les tables. PHPUnit va exécuter un TRUNCATE sur toutes les tables que vous avez indiquées pour les remettre à l'état vide. 2. Configurer les fixtures PHPUnit va parcourir toutes les lignes de fixture indiquées et les insérer dans leurs tables respectives. Tester des bases de données 86 3?5. Exécuter les tests, vérifier les résultats et nettoyer Une fois la base de données réinitialisée et remise dans son état de départ, le test en tant que tel est exécuté par PHPUnit. Cette partie du code de test ne nécessite pas du tout de s'occuper de l'extension base de données, vous pouvez procéder et tester tout ce que vous voulez dans votre code. Votre test peut utiliser une assertion spéciale appelée assertDataSetsEqual() à des fins de vérification, mais c'est totalement facultatif. Cette fonctionnalité sera expliquée dans la section « Assertions pour les bases de données ». Configuration d'un cas de test de base de données PHPUnit Habituellement quand vous utilisez PHPUnit, vos cas de tests devraient hériter de la classe PHPUnit_Framework_TestCase de la façon suivante : require_once "PHPUnit/Framework/TestCase.php"; class MonTest extends PHPUnit_Framework_TestCase { public function testCalculate() { $this->assertEquals(2, 1 + 1); } } Si vous voulez tester du code qui fonctionne avec l'extension base de données, le setup sera un peu plus complexe et vous devrez hériter d'un cas de test abstrait différent qui nécessite que vous implémentiez deux méthodes abstraites getConnection() et getDataSet(): require_once "PHPUnit/Extensions/Database/TestCase.php"; class MonLivreDOrTest extends PHPUnit_Extensions_Database_TestCase { /** * return PHPUnit_Extensions_Database_DB_IDatabaseConnection */ public function getConnection() { $pdo = new PDO('sqlite::memory:'); return $this->createDefaultDBConnection($pdo, ':memory:'); } /** * return PHPUnit_Extensions_Database_DataSet_IDataSet */ public function getDataSet() { return $this->createFlatXMLDataSet(dirname(__FILE__).'/_files/guestbook-seed.xml' } } Implémenter getConnection() Pour permettre aux fonctionnalités de nettoyage et de chargement des fixtures de fonctionner, l'extension de base de données PHPUnit nécessite d'accéder à une connexion de base de données abstraite pour les différents types via la bibliothèque PDO. Il est important de noter que votre Tester des bases de données 87 application n'a pas besoin de s'appuyer sur PDO pour utiliser l'extension de base de données de PHPUnit, la connexion est principalement utilisée pour le nettoyage et la configuration de setup. Dans l'exemple précédent, nous avons créé une connexion Sqlite en mémoire et nous l'avons passé à la méthode createDefaultDBConnection qui encapsule l'instance PDO et le second paramètre (le nom de la base de données) dans une couche d'abstraction très simple pour connexion aux bases de données du type PHPUnit_Extensions_Database_DB_IDatabaseConnection. La section « Utiliser la connexion de base de données » explicite l'API de cette interface et comment en faire le meilleur usage. Implémenter getDataSet() La méthode getDataSet() définit à quoi doit ressembler l'état initial de la base de données avant que chaque test ne soit exécuté. L'état de la base de données est abstrait par les concepts DataSet et DataTable, tous les deux représentés par les interfaces PHPUnit_Extensions_Database_DataSet_IDataSet et PHPUnit_Extensions_Database_DataSet_IDataTable. La prochaine section décrira en détail comment ces concepts fonctionnent et quels sont les avantages à les utiliser lors des tests de base de données. Pour l'implémentation, nous avons seulement besoin de savoir que la méthode getDataSet() est appelée une fois dans setUp() pour récupérer l'ensemble de données de la fixture et l'insérer dans la base de données. Dans l'exemple, nous utilisons une méthode fabrique createFlatXMLDataSet($filename) qui représente un ensemble de données à l'aide d'une représentation XML. Qu'en est-il du schéma de base de données (DDL)? PHPUnit suppose que le schéma de base de données avec toutes ses tables, ses triggers, séquences et vues est créé avant qu'un test soit exécuté. Cela signifie que vous, en tant que développeur, devez vous assurer que la base de données est correctement configurée avant de lancer la suite de tests. Il y a plusieurs moyens pour satisfaire cette condition préalable au test de base de données. 1. Si vous utilisez une base de données persistante (pas Sqlite en mémoire) vous pouvez facilement configure la base de données avec des outils tels que phpMyAdmin pour MySQL et réutiliser la base de données pour chaque exécution de test. 2. Si vous utilisez des bibliothèques comme Doctrine 2 [http://www.doctrine-project.org] ou Propel [http://www.propelorm.org/] vous pouvez utiliser leurs APIs pour créer le schéma de base de données dont vous avez besoin une fois avant de lancer vos tests. Vous pouvez utiliser les possibilités apportées par l'amorce et la configuration de PHPUnit [http://www.phpunit.de/manual/ current/en/textui.html] pour exécuter ce code à chaque fois que vos tests sont exécutés. Astuce: utilisez votre propre cas de tests abstrait de base de données En partant des exemples d'implémentation précédents, vous pouvez facilement voir que la méthode getConnection() est plutôt statique et peut être réutilisée dans différents cas de test de base de données. Additionnellement pour conserver de bonnes performances pour vos tests et maintenir la charge de la base de données basse vous pouvez refactoriser un peu le code pour obtenir un cas de test abstrait générique pour votre application, qui vous permette encore d'indiquer des données de fixture différentes pour chaque cas de test : require_once "PHPUnit/Extensions/Database/TestCase.php"; abstract class MyApp_Tests_DatabaseTestCase extends PHPUnit_Extensions_Database_TestCase { Tester des bases de données 88 // instancie pdo seulement une fois pour le nettoyage du test/le chargement de la fix static private $pdo = null; // instancie PHPUnit_Extensions_Database_DB_IDatabaseConnection seulement une fois pa private $conn = null; final public function getConnection() { if ($this->conn === null) { if (self::$pdo == null) { self::$pdo = new PDO('sqlite::memory:'); } $this->conn = $this->createDefaultDBConnection(self::$pdo, ':memory:'); } return $this->conn; } } Mais la connexion à la base de données reste codée en dur dans la connexion PDO. PHPUnit possède une autre fonctionnalité formidable qui peut rendre ce cas de test encore plus générique. Si vous utilisez la configuration XML [http://www.phpunit.de/manual/current/en/ appendixes.configuration.html#appendixes.configuration.php-ini-constants-variables], vous pouvez rendre la connexion à la base de données configurable pour chaque exécution de test. Créons d'abord un fichier « phpunit.xml » dans le répertoire tests/ de l'application qui ressemble à ceci : <?xml version="1.0" encoding="UTF-8" ?> <phpunit> <php> <var name="DB_DSN" value="mysql:dbname=myguestbook;host=localhost" /> <var name="DB_USER" value="user" /> <var name="DB_PASSWD" value="passwd" /> <var name="DB_DBNAME" value="myguestbook" /> </php> </phpunit> Nous pouvons maintenant modifier notre cas de test pour qu'il ressemble à ça : abstract class Generic_Tests_DatabaseTestCase extends PHPUnit_Extensions_Database_TestCas { // instancie pdo seulement une fois pour le nettoyage du test/le chargement de la fix static private $pdo = null; // instancie PHPUnit_Extensions_Database_DB_IDatabaseConnection seulement une fois pa private $conn = null; final public function getConnection() { if ($this->conn === null) { if (self::$pdo == null) { self::$pdo = new PDO( $GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS[' } $this->conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAM } return $this->conn; } } Nous pouvons maintenant lancer la suite de tests de la base de données en utilisant différentes configurations depuis l'interface en ligne de commandes: userdesktop> phpunit --configuration developer-a.xml MesTests/ Tester des bases de données 89 userdesktop> phpunit --configuration developer-b.xml MesTests/ La possibilité de lancer facilement des tests de base de données sur différentes bases de données cibles est très important si vous développez sur une machine de développement. Si plusieurs développeurs exécutent les tests de base de données sur la même connexion de base de données, vous pouvez facilement faire l'expérience d'échec de tests du fait des concurrences d'accès. Comprendre DataSets et DataTables Un concept centre de l'extension de base de données PHPUnit sont les DataSets et les DataTables. Vous devez comprendre ce simple concept pour maîtriser les tests de bases de données avec PHPUnit. Les DataSets et les DataTables constituent une couche d'abstraction sur les tables, les lignes et les colonnes de la base de données. Une simple API cache le contenu de la base de données sous-jacente dans une structure objet, qui peut également être implémentée par d'autres sources qui ne sont pas des bases de données. Cette abstraction est nécessaire pour comparer le contenu constaté d'une base de données avec le contenu attendu. Les attentes peuvent être représentées dans des fichiers XML, YAML ou CSV ou des tableaux PHP par exemple. Les interfaces DataSets et DataTables permettent de comparer ces sources conceptuellement différentes en émulant un stockage en base de données relationnelle dans une approche sémantiquement similaire. Un processus pour des assertions de base de données dans vos tests se limitera alors à trois étapes simples : ? Indiquer une ou plusieurs tables dans votre base de données via leurs noms de table (ensemble de données constatées) ? Indiquez l'ensemble de données attendu dans votre format préféré (YAML, XML, ..) ? Affirmez que les représentations des deux ensembles de données sont égaux. Les assertions ne constituent pas le seul cas d'utilisation des DataSets et DataTables dans l'extension de base de données PHPUnit. Comme illustré dans la section précédente, ils décrivent également le contenu initial de la base de données. Vous êtes obligés de définir un ensemble de données fixture avec le cas de test Database, qui est ensuite utilisé pour : ? Supprimer toutes les lignes des tables indiquées dans le DataSet. ? Ecrire toutes les lignes dans les tables de données dans la base de données. Implémentations disponibles Il existe trois types différents de datasets/datatables: ? DataSets et DataTables basés sur des fichiers ? DataSets et DataTables basés sur des requêtes ? DataSets et DataTables de filtre et de combinaison les datasets et les tables basés sur des fichiers sont généralement utilisés pour la fixture initiale et pour décrire l'état attendu d'une base de données. DataSet en XML à plat Le dataset le plus commun est appelé XML à plat (flat XML). C'est un format xml très simple dans lequel une balise à l'intérieur d'un noeud racine <dataset> représente exactement une ligne de la base de données. Les noms des balises sont ceux des tables dans lesquelles insérer les lignes et un attribut représente la colonne. Un exemple pour une simple application de livre d'or pourrait ressembler à ceci : Tester des bases de données 90 <?xml version="1.0" ?> <dataset> <livre_d_or id="1" contenu="Salut Poum!" utilisateur="joe" date_creation="2010-04-24 <livre_d_or id="2" contenu="J'aime !" utilisateur="nancy" date_creation="2010-04-26 1 </dataset> C'est à l'évidence facile à écrire. Ici, <livre_d_or> est le nom de la table dans laquelle les deux lignes sont insérées, chacune avec quatre colonnes « id », « contenu », « utilisateur » et « date_creation » et leurs valeurs respectives. Cependant, cette simplicité a un coût. Avec l'exemple précédent, difficile de voir comment nous devons indiquer une table vide. Vous pouvez insérer une balise avec aucun attribut contenant le nom de la table vide. Un fichier XML à plat pour une table livre_d_or pourrait alors ressembler à ceci: <?xml version="1.0" ?> <dataset> <livre_d_or /> </dataset> La gestion des valeurs NULL avec le dataset en XML à plat est fastidieuse. Une valeur NULL est différente d'une chaîne vide dans la plupart des bases de données (Oracle étant une exception), quelque chose qu'il est difficile de décrire dans le format XML à plat. Vous pouvez représenter une valeur NULL en omettant d'attribut indiquant la ligne. Si votre livre d'or autorise les entrées anonymes représentées par une valeur NULL dans la colonne utilisateur, un état hypothétique de la table livre_d_or pourrait ressembler à ceci: <?xml version="1.0" ?> <dataset> <livre_d_or id="1" contenu="Hello Poum !" utilisateur="joe" date_creation="2010-04-24 <livre_d_or id="2" contenu="J'aime !" date_creation="2010-04-26 12:14:20" /> </dataset> Dans ce cas, la seconde entrée est postée anonymement. Cependant, ceci conduit à un sérieux problème pour la reconnaissance de la colonne. Lors des assertions d'égalité de datasets, chaque dataset doit indiquer quelle colonne une table contient. Si un attribut est NULL pour toutes les lignes de la data- table, comment l'extension de base de données sait que la colonne doit faire partie de la table ? Le dataset en XML à plat fait maintenant une hypothèse cruciale en décrétant que les attributs de la première ligne définie pour une table définissent les colonnes de cette table. Dans l'exemple précédent, ceci signifierait que « id », « contenu », « utilisateur » et « date_creation » sont les colonnes de la table livre_d_or. Pour la seconde ligne dans laquelle « utilisateur » n'est pas défini, un NULL sera inséré dans la base de données. Quand la première entrée du livre d'or est supprimée du dataset, seuls « id », « contenu » et « date_creation » seront des colonnes de la table livre_d_or, puisque « utilisateur » n'est pas indiqué. Pour utiliser efficacement le dataset au format XML à plat quand des valeurs NULL sont pertinentes, la première ligne de chaque table ne doit contenir aucune valeur NULL, seules les lignes suivantes pouvant omettre des attributs. Ceci peut s'avérer délicat, puisque l'ordre des lignes est un élément pertinent pour les assertions de base de données. A l'inverse, si vous n'indiquez qu'un sous-élément des colonnes de la table dans le dataset au format XML à plat, toutes les valeurs omises sont positionnées à leurs valeurs par défaut. Ceci provoquera des erreurs si l'une des valeurs omises est définie par « NOT NULL DEFAULT NULL ». En conclusion, je ne peux que vous conseiller de n'utiliser les datasets au format XML à plat que si vous n'avez pas besoin des valeurs NULL. Tester des bases de données 91 Vous pouvez créer une instance de dataset au format XML à plat dans votre cas de test de base de données en appelant la méthode createFlatXmlDataSet($filename): class MonCasDeTest extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { return $this->createFlatXmlDataSet('maFixtureAuFormatXMLaPlat.xml'); } } DataSet XML Il existe un autre dataset XML davantage structuré, qui est un peu plus verbeux à écrire mais qui évite les problèmes de NULL du dataset au format XML à plat. Dans le noeud racine <dataset> vous pouvez indiquer les balises <table>, <column>, <row>, <value> et <null />. Un dataset équivalent à celui défini précédemment pour le livre d'or en format XML à plat ressemble à : <?xml version="1.0" ?> <dataset> <table name="livre_d_or"> <column>id</column> <column>contenu</column> <column>utilisateur</column> <column>date_creation</column> <row> <value>1</value> <value>Hello Poum !</value> <value>joe</value> <value>2010-04-24 17:15:23</value> </row> <row> <value>2</value> <value>J'aime !</value> <null /> <value>2010-04-26 12:14:20</value> </row> </table> </dataset> Tout <table> défini possède un nom et nécessite la définition de toutes les colonnes avec leurs noms. Il peut contenir zéro ou tout nombre positif d'éléments <row> imbriqués. Ne définir aucun élément <row> signifie que la table est vide. Les balises <value> et <null /> doivent être indiquées dans l'ordre des éléments <column>précédemment donnés. La balise <null /> signifie évidemment que la valeur est NULL. Vous pouvez créer une instance de dataset xml dans votre cas de test de base de données en appelant la méthode createXmlDataSet($filename) : class MonCasdeTest extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { return $this->createXMLDataSet('maFixtureyXml.xml'); } } XML DataSet XML MySQL Ce nouveau format XML est spécifique au serveur de bases de données MySQL [http:// www.mysql.com]. Sa gestion a été ajoutée dans PHPUnit 3.5. Les fichiers écrits ce format peuvent être Tester des bases de données 92 générés avec l'utilitaire mysqldump [http://dev.mysql.com/doc/refman/5.0/en/mysqldump.html]. Contrairement aux datasets CSV, que mysqldump gère également, un unique fichier de ce format XML peut contenir des données pour de multiples tables. Vous pouvez créer un fichier dans ce format en invoquant mysqldump de cette façon : mysqldump --xml -t -u [nom_utilisateur] --password=[mot_de_passe] [base_de_donnees] > /ch Ce fichier peut être utilisé dans votre case de test de base de données en appelant la méthode createMySQLXMLDataSet($nomdefichier): class MonCasDeTest extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { return $this->createMySQLXMLDataSet('/chemin/vers/fichier.xml'); } } DataSet YAML Nouveau depuis PHUnit 3.4, la possibilité d'indiquer un dataset via le format populaire YAML. Pour que cela fonctionne, vous devez installer PHPUnit 3.4 avec PEAR ainsi que sa dépendance optionnelle Symfony YAML. Vous pouvez ensuite écrire un dataset YAML pour l'exemple du livre d'or: livre_d_or: - id: 1 contenu: "Hello Poum !" utilisateur: "joe" date_creation: 2010-04-24 17:15:23 - id: 2 contenu: "J'aime !" utilisateur: date_creation: 2010-04-26 12:14:20 C'est simple, pratique et ça règle le problème de NULL que pose le dataset équivalent au format XML à plat. Un NULL en YAML s'exprime simplement en donnant le nom de la colonne dans indiquer de valeur. Une chaîne vide est indiquée par colonne1: "". Le dataset YAML ne possède pas actuellement de méthode de fabrique pour le cas de tests de base de données, si bien que vous devez l'instancier manuellement : require_once "PHPUnit/Extensions/Database/DataSet/YamlDataSet.php"; class LivredOrYamlTest extends PHPUnit_Extensions_Database_TestCase { protected function getDataSet() { return new PHPUnit_Extensions_Database_DataSet_YamlDataSet( dirname(__FILE__)."/_files/livre_d_or.yml" ); } } DataSet CSV Un autre dataset au format fichier est basé sur les fichiers CSV. Chaque table du dataset est représenté par un fichier CSV. Pour notre exemple de livre d'or, nous pourrions définir un fichier livre_d_or- table.csv: Tester des bases de données 93 id;contenu;utilisateur;date_creation 1;"Hello Poum !";"joe";"2010-04-24 17:15:23" 2;"J'aime !""nancy";"2010-04-26 12:14:20" Bien que ce soit très pratique à éditer avec Excel ou LibreOffice, vous ne pouvez pas indiquer de valeurs NULL avec le dataset CSV. Une colonne vide conduira à ce que la valeur vide par défaut de la base de données soit insérée dans la colonne. Vous pouvez créer un dataset CSV en appelant : require_once 'PHPUnit/Extensions/Database/DataSet/CsvDataSet.php'; class CsvLivredOrTest extends PHPUnit_Extensions_Database_TestCase { protected function getDataSet() { $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet(); $dataSet->addTable('guestbook', dirname(__FILE__)."/_files/livre_d_or.csv"); return $dataSet; } } DataSet tableau Il n'existe pas (encore) de DataSet basé sur les tableau dans l'extension base de données de PHPUnit, mais vous pouvez implémenter facilement la vôtre. Notre exemple du Livre d'or devrait ressembler à : class LivredOrTableauTest extends PHPUnit_Extensions_Database_TestCase { protected function getDataSet() { return new MyApp_DbUnit_ArrayDataSet(array( 'livre_d_or' => array( array('id' => 1, 'contenu' => 'Hello Poum !', 'utilisateur' => 'joe', 'da array('id' => 2, 'contenu' => 'J\'aime !', 'utilisateur' => null, 'dat ), )); } } Un DataSet PHP possède des avantages évidents sur les autres datasets utilisant des fichiers : ? Les tableaux PHP peuvent évidemment gérer les valeurs NULL. ? Vous n'avez pas besoin de fichiers additionnels pour les assertions et vous pouvez les renseigner directement dans les cas de test. Pour que ce dataset ressemble aux DataSets au format XML à plat, CSV et YAML, les clefs de la première ligne spécifiée définissent les noms de colonne de la table, dans le cas précédent, ce serait « contenu », « utilisateur » et « date_creation ». L'implémentation de ce DataSet tableau est simple et évidente: require_once 'PHPUnit/Util/Filter.php'; require_once 'PHPUnit/Extensions/Database/DataSet/AbstractDataSet.php'; require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTableIterator.php'; require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTable.php'; require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTableMetaData.php'; PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT'); Tester des bases de données 94 class MyApp_DbUnit_ArrayDataSet extends PHPUnit_Extensions_Database_DataSet_AbstractDataS { /** * var array */ protected $tables = array(); /** * param array $data */ public function __construct(array $data) { foreach ($data AS $tableName => $rows) { $columns = array(); if (isset($rows[0])) { $columns = array_keys($rows[0]); } $metaData = new PHPUnit_Extensions_Database_DataSet_DefaultTableMetaData($tab $table = new PHPUnit_Extensions_Database_DataSet_DefaultTable($metaData); foreach ($rows AS $row) { $table->addRow($row); } $this->tables[$tableName] = $table; } } protected function createIterator($reverse = FALSE) { return new PHPUnit_Extensions_Database_DataSet_DefaultTableIterator($this->tables } public function getTable($tableName) { if (!isset($this->tables[$tableName])) { throw new InvalidArgumentException("$tableName ne correspond pas à une table } return $this->tables[$tableName]; } } Query (SQL) DataSet Pour les assertions de base de données, vous n'avez pas seulement besoin de datasets basés sur des fichiers mais aussi de Datasets basé sur des requêtes/du SQL qui contiennent le contenu constaté de la base de données. C'est là que le DataSet Query s'illustre : $ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); $ds->addTable('livre_d_or'); Ajouter une table juste par son nom est un moyen implicite de définir la table de données avec la requête suivante : $ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); $ds->addTable('livre_d_or', 'SELECT * FROM livre_d_or'); Vous pouvez utiliser ceci en indiquant des requêtes arbitraires pour vos tables, par exemple en restreignant les lignes, les colonnes ou en ajoutant des clauses ORDER BY: $ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); Tester des bases de données 95 $ds->addTable('livre_d_or', 'SELECT id, contenu FROM livre_d_or ORDER BY date_creation DE La section relative aux assertions de base de données montrera plus en détails comment utiliser le Query DataSet. Dataset de base de données En accédant à la connexion de test, vous pouvez créer automatiquement un DataSet constitué de toutes les tables et de leur contenu de la base de données indiquée comme second paramètre de la méthode fabrique de connexion. Vous pouvez, soit créer un dataset pour la base de données complète comme montré dans la méthode testLivredOr(), soit le restreindre à un ensemble de noms de tables avec une liste blanche comme montré dans la méthode testLivredOrFiltre(). class MonLivredOrSqlTest extends PHPUnit_Extensions_Database_TestCase { /** * return PHPUnit_Extensions_Database_DB_IDatabaseConnection */ public function getConnection() { $base_de_donnees = 'ma_base_de_donnee'; $pdo = new PDO('mysql:...', $utilisateur, $mot_de_passe); return $this->createDefaultDBConnection($pdo, $base_de_donnees); } public function testLivredOr() { $dataSet = $this->getConnection()->createDataSet(); // ... } public function testLivredOrFiltre() { $nomTables = array('livre_d_or'); $dataSet = $this->getConnection()->createDataSet($nomTables); // ... } } DataSet de remplacement J'ai évoqué les problèmes de NULL avec les DataSet au format XML à plat et CSV, mais il y existe un contournement légèrement compliqué pour que ces deux types de datasets fonctionnent avec NULLs. Le DataSet de remplacement est un décorateur pour un dataset existant et vous permet de remplacer des valeurs dans toute colonne du dataset par une autre valeur de remplacement. Pour que notre exemple de livre d'or fonctionne avec des valeurs NULL nous indiquons le fichier comme ceci: <?xml version="1.0" ?> <dataset> <livre_d_or id="1" contenu="Hello Poum !" utilisateur="joe" date_creation="2010-04-24 <livre_d_or id="2" contenu="J'aime !" utilisateur="##NULL##" date_creation="2010-04-2 </dataset> Nous enrobons le DataSet au format XML à plat dans le DataSet de remplacement : require_once 'PHPUnit/Extensions/Database/DataSet/ReplacementDataSet.php'; class ReplacementTest extends PHPUnit_Extensions_Database_TestCase { Tester des bases de données 96 public function getDataSet() { $ds = $this->createFlatXmlDataSet('maFixtureEnXMLaPlat.xml'); $rds = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($ds); $rds->addFullReplacement('##NULL##', null); return $rds; } } Filtre de DataSet Si vous avez un fichier de fixture conséquent vous pouvez utiliser le filtre de DataSet pour des listes blanches ou noires des tables et des colonnes qui peuvent être contenues dans un sous-dataset. C'est particulièrement commode en combinaison avec le DataSet de base de données pour filtrer les colonnes des datasets. class DataSetFilterTest extends PHPUnit_Extensions_Database_TestCase { public function testLivredOrAvecFiltredInclusion() { $nomTables = array('livre_d_or'); $dataSet = $this->getConnection()->createDataSet(); $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet); $filterDataSet->addIncludeTables(array('livre_d_or')); $filterDataSet->setIncludeColumnsForTable('livre_d_or', array('id', 'contenu')); // .. } public function testLivredOrAvecFiltredExclusion() { $nomTables = array('livre_d_or'); $dataSet = $this->getConnection()->createDataSet(); $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet); $filterDataSet->addExcludeTables(array('foo', 'bar', 'baz')); // ne garder que la $filterDataSet->setExcludeColumnsForTable('livre_d_or', array('utilisateur', 'dat // .. } } NOTE Vous ne pouvez pas utiliser en même temps le filtrage de colonne d'inclusion et d'exclusion sur la même table, seulement sur des tables différentes. De plus, il est seulement possible d'appliquer soit une liste blanche, soit une liste noire aux tables, mais pas les deux à la fois. DataSet composite Le DataSet composite est très utile pour agréger plusieurs datasets déjà existants dans un unique dataset. Quand plusieurs datasets contiennent la même table, les lignes sont ajoutées dans l'ordre indiqué. Par exemple, si nous avons deux datasets fixture1.xml : <?xml version="1.0" ?> <dataset> <livre_d_or id="1" contenu="Hello Poum !" utilisateur="joe" date_creation="2010-04-24 </dataset> et fixture2.xml: <?xml version="1.0" ?> <dataset> Tester des bases de données 97 <livre_d_or id="2" contenu="J'aime !" utilisateur="##NULL##" date_creation="2010-04-2 </dataset> En utiliser le DataSet composite, nous pouvons agréger les deux fichiers de fixture: class CompositeTest extends PHPUnit_Extensions_Database_TestCase { public function getDataSet() { $ds1 = $this->createFlatXmlDataSet('fixture1.xml'); $ds2 = $this->createFlatXmlDataSet('fixture2.xml'); $compositeDs = new PHPUnit_Extensions_Database_DataSet_CompositeDataSet(); $compositeDs->addDataSet($ds1); $compositeDs->addDataSet($ds2); return $compositeDs; } } Attention aux clefs étrangères Lors du SetUp de la fixture l'extension de base de données de PHPUnit insère les lignes dans la base de données dans l'ordre où elles sont indiquées dans votre fixture. Si votre schéma de base de données utilise des clefs étrangères, ceci signifie que vous devez indiquer les tables dans un ordre qui ne provoquera pas une violation de contrainte pour ces clefs étrangères. Implementer vos propres DataSets/DataTables Pour comprendre le fonctionnement interne des DataSets et des DataTables jetons un oeil sur l'interface d'un DataSet. Vous pouvez sauter cette partie si vous ne projetez pas d'implémenter votre propre DataSet ou DataTable. interface PHPUnit_Extensions_Database_DataSet_IDataSet extends IteratorAggregate { public function getTableNames(); public function getTableMetaData($nomTable); public function getTable($nomTable); public function assertEquals(PHPUnit_Extensions_Database_DataSet_IDataSet $autre); public function getReverseIterator(); } L'interface publique est utilisée en interne par l'assertion assertDataSetsEqual() du cas de test de base de données pour contrôler la qualité du dataset. De l'interface IteratorAggregate le IDataSet hérite la méthode getIterator() pour parcourir toutes les tables du dataset. La méthode additionnelle d'itérateur inverse est nécessaire pour réussir à tronquer les tables dans l'ordre inverse à celui indiqué. Pour comprendre le besoin d'un itérateur inverse, pensez deux tables (TableA et TableB) avec TableB qui contient une clef étrangère sur une colonne de TableA. Si pour la configuration de la fixture une ligne est insérée dans TableA puis un enregistrement dépendant dans TableB, alors il est évident que pour détruire le contenu de toutes les tables, l'ordre inverse va vous poser des problèmes avec les contraintes de clefs étrangères. En fonction de l'implémentation, différentes approches sont prises pour ajouter des instances de table dans un dataset. Par exemple, les tables sont ajoutées de façon interne lors de la construction depuis le fichier source dans tous les datasets basés sur les fichiers comme YamlDataSet, XmlDataSet ou FlatXmlDataSet. Tester des bases de données 98 Une table est également représentée par l'interface suivante : interface PHPUnit_Extensions_Database_DataSet_ITable { public function getTableMetaData(); public function getRowCount(); public function getValue($row, $column); public function getRow($row); public function assertEquals(PHPUnit_Extensions_Database_DataSet_ITable $other); } Mise à part la méthode getTableMetaData(), ça parle plutôt de soi-même. Les méthodes utilisées sont toutes nécessaires pour les différentes assertions de l'extension Base de données expliquées dans le chapitre suivant. La méthode getTableMetaData() doit retourner une implémentation de l'interface PHPUnit_Extensions_Database_DataSet_ITableMetaData qui décrit la structure de la table. Elle contient des informations sur: ? Le nom de la table ? Un tableau des noms de colonne de la table, classé par leur ordre d'apparition dans l'ensemble résultat. ? Un tableau des colonnes clefs primaires. Cette interface possède également une assertion qui contrôle si deux instances des méta données des tables sont égales et qui sera utilisée par l'assertion d'égalité d'ensemble de données. L'API de connexion Il y a trois méthodes intéressantes dans l'interface de connexion qui doit être retournée par la méthode getConnection() du cas de test de base de données : interface PHPUnit_Extensions_Database_DB_IDatabaseConnection { public function createDataSet(Array $tableNames = NULL); public function createQueryTable($resultName, $sql); public function getRowCount($tableName, $whereClause = NULL); // ... } 1. La méthode createDataSet() crée un DataSet de base de données (DB) comme décrit dans la section relative aux implémentations de DataSet. class ConnectionTest extends PHPUnit_Extensions_Database_TestCase { public function testCreateDataSet() { $tableNames = array('livre_d_or'); $dataSet = $this->getConnection()->createDataSet(); } } 2. La méthode createQueryTable() peut être utilisée pour créer des instances d'une QueryTable, en lui passant un nom de résultat et une requête SQL. C'est une méthode pratique quand elle est associée à des assertions résultats/table comme cela sera illustré dans la prochaine section relative à l'API des assertions de base de données. class ConnectionTest extends PHPUnit_Extensions_Database_TestCase { Tester des bases de données 99 public function testCreateQueryTable() { $nomTables = array('livre_d_or'); $queryTable = $this->getConnection()->createQueryTable('livre_d_or', 'SELECT * } } 3. La méthode getRowCount() est un moyen pratique d'accéder au nombre de lignes d'une table, éventuellement filtrées par une clause where supplémentaire. Ceci peut être utilisé pour une simple assertion d'égalité : class ConnectionTest extends PHPUnit_Extensions_Database_TestCase { public function testGetRowCount() { $this->assertEquals(2, $this->getConnection()->getRowCount('livre_d_or')); } } API d'assertion de base de données En tant qu'outil de test, l'extension base de données fournit certainement des assertions que vous pouvez utiliser pour vérifier l'état actuel de la base de données, des tables et du nombre de lignes des tables. Cette section décrit ces fonctionnalités en détail : Faire une assertion sur le nombre de lignes d'une table Il est souvent très utile de vérifier si une table contient un nombre déterminé de lignes. Vous pouvez facilement réaliser cela sans code de liaison supplémentaire en utilisant l'API de connexion. Disons que nous voulons contrôler qu'après une insertion d'une ligne dans notre livre d'or, nous n'avons plus seulement nos deux entrées initiales qui nous ont accompagnées dans tous les exemples précédents, mais aussi une troisième : class LivredorTest extends PHPUnit_Extensions_Database_TestCase { public function testAddEntry() { $this->assertEquals(2, $this->getConnection()->getRowCount('livre_d_or'), "Pré-Co $livre_d_or = new Livredor(); $livre_d_or->addEntry("suzy", "Hello world!"); $this->assertEquals(3, $this->getConnection()->getRowCount('livre_d_or'), "Insert } } Faire une assertion sur l'état d'une table L'assertion précédent est utile, mais nous voudrons certainement tester le contenu présent de la table pour vérifier que toutes les valeurs ont été écrites dans les bonnes colonnes. Ceci peut être réalisé avec une assertion de table. Pour cela, nous devons définir une instance de Query Table qui tire son contenu d'un nom de table et d'une requête SQL et le compare à un DataSet basé sur un fichier/tableau. class LivredorTest extends PHPUnit_Extensions_Database_TestCase { Tester des bases de données 100 public function testAddEntry() { $livre_d_or = new Livredor(); $livre_d_or->addEntry("suzy", "Hello world!"); $requeteDeTable = $this->getConnection()->createQueryTable( 'livre_d_or', 'SELECT * FROM livre_d_or' ); $tableAttendue = $this->createFlatXmlDataSet("livreAttendu.xml") ->getTable("livre_d_or"); $this->assertTablesEqual($tableAttendue, $requeteDeTable); } } Maintenant, nous devons écrire le fichier XML à plat livreAttendu.xml pour cette assertion : <?xml version="1.0" ?> <dataset> <livre_d_or id="1" contenu="Hello Poum !" utilisateur="joe" date_creation="2010-04-24 <livre_d_or id="2" contenu="J'aime !" utilisateur="nancy" date_creation="2010-04-26 1 <livre_d_or id="3" contenu="Hello world!" utilisateur="suzy" date_creation="2010-05-0 </dataset> Cette assertion ne réussira que si elle est lancée très exactement le 2010?05?01 21:47:08. Les dates posent un problème spécial pour le test de base de données et nous pouvons contourner l'échec en omettant la colonne « date_creation » de l'assertion. Le fichier au format XML à plat adapté livreAttendu.xml devra probablement ressembler à ce qui suit pour que l'assertion réussisse. <?xml version="1.0" ?> <dataset> <livre_d_or id="1" contenu="Hello Poum !" utilisateur="joe" /> <livre_d_or id="2" contenu="J'aime !" utilisateur="nancy" /> <livre_d_or id="3" contenu="Hello world!" utilisateur="suzy" /> </dataset> Nous devons corriger l'appel à Query Table: $queryTable = $this->getConnection()->createQueryTable( 'livre_d_or', 'SELECT id, contenu, utilisateur FROM livre_d_or' ); Faire une assertion sur le résultat d'une requête Vous pouvez également faire une assertion sur le résultat de requêtes complexes avec l'approche Query Table, simplement en indiquant le nom d'un résultat avec une requête et en le comparant avec un ensemble de données: class ComplexQueryTest extends PHPUnit_Extensions_Database_TestCase { public function testComplexQuery() { $requeteTable = $this->getConnection()->createQueryTable( 'maRequeteComplexe', 'SELECT requeteComplexe..' ); $tableAttendue = $this->createFlatXmlDataSet("assertionDeRequeteComplexe.xml") ->getTable("myComplexQuery"); $this->assertTablesEqual($tableAttendue, $requeteTable); } } Tester des bases de données 101 Faire une assertion sur l'état de plusieurs tables Evidemment, vous pouvez faire une assertion sur l'état de plusieurs tables à la fois et comparer un ensemble de données obtenu par une requête avec un ensemble de données basé sur un fichier. Il y a deux façons différentes de faire des assertions de DataSet. 1. Vous pouvez utiliser le Database (DB) Dataset à partir de la connexion et le comparer au DataSet basé sur un fichier. class DataSetAssertionsTest extends PHPUnit_Extensions_Database_TestCase { public function testCreateDataSetAssertion() { $dataSet = $this->getConnection()->createDataSet(array('livre_d_or')); $dataSetAttendu = $this->createFlatXmlDataSet('livre_d_or.xml'); $this->assertDataSetsEqual($dataSetAttendu, $dataSet); } } 2. Vous pouvez construire vous-même le DataSet: class DataSetAssertionsTest extends PHPUnit_Extensions_Database_TestCase { public function testManualDataSetAssertion() { $dataSet = new PHPUnit_Extensions_Database_DataSet_QueryDataSet(); $dataSet->addTable('livre_d_or', 'SELECT id, contenu, utilisateur FROM livre_d $dataSetAttendu = $this->createFlatXmlDataSet('livre_d_or.xml'); $this->assertDataSetsEqual($dataSetAttendu, $dataSet); } } Foire aux questions PHPUnit va-t'il (re-)créer le schéma de base de données pour chaque test ? Non, PHPUnit exige que tous les objets de base de données soit disponible quand la suite démarre. La base de données, les tables, les séquences, les triggers et les vues doivent être créés avant que vous exécutiez la suite de tests. Doctrine 2 [http://www.doctrine-project.org] ou eZ Components [http://www.ezcomponents.org] possèdent des outils puissants qui vous permettent de créer le schéma de base de données à partir de structures de données définies préalablement, cependant, ceux-ci doivent être reliés à l'extension PHPUnit pour permettre la recréation automatique de la base de données avant que la suite de tests complète ne soit exécutée. Puisque chaque test nettoie complètement la base de données, vous n'avez même pas obligation de re-créer la base de donnée pour chaque exécution des tests. Une base de données disponible de façon permanente fonctionne parfaitement. Suis-je obligé d'utiliser PDO dans mon application pour que l'extension de base de données fonctionne ? Non, PDO n'est nécessaire que pour le nettoyage et la configuration de la fixture et pour les assertions. Vous pouvez utiliser n'importe laquelle des abstractions de base de données que vous voulez dans votre propre code. Tester des bases de données 102 Que puis-je faire quand j'obtiens une erreur « Too much Connections (Trop de connexions) » ? Si vous ne mettez pas en cache l'instance PDO qui est créée dans la méthode getConnection() du cas de test le nombre de connexions à la base de données est augmenté d'une unité ou plus pour chaque test de base de données. Avec la configuration par défaut, MySql n'autorise qu'un maximum de 100 connexions concurrentes. Les autres moteurs de bases de données possèdent également des limites du nombre maximum de connexions. La sous-section « Utilisez votre propre cas de test de base de données abstrait » illustre comment vous pouvez empêcher cette erreur de survenir en utilisant une unique instance de PDO en cache dans tous vos tests. Comment gérer les valeurs NULL avec les DataSets au format XML à plat / CSV ? Ne le fait pas. Pour cela, vous devez utiliser des DataSets XML ou YAML. 103 Chapitre 9. Tests incomplets et sautés Tests incomplets Quand vous travaillez sur une nouvelle classe de cas de test, vous pourriez vouloir commencer en écrivant des méthodes de test vides comme public function testQuelquechose() { } pour garder la trace des tests que vous avez à écrire. Le problème avec les méthodes de test vides est qu'elles sont interprétées comme étant réussies par le framework PHPUnit. Cette mauvaise interprétation fait que le rapport de tests devient inutile -- vous ne pouvez pas voir si un test est effectivement réussi ou s'il n'a tout simplement pas été implémenté. Appeler $this->fail() dans une méthode de test non implémentée n'aide pas davantage, puisqu'alors le test sera interprété comme étant un échec. Ce serait tout aussi faux que d'interpréter un test non implémenté comme étant réussi. Si nous pensons à un test réussi comme à un feu vert et à un échec de test comme à un feu rouge, nous avons besoin d'un feu orange additionnel pour signaler un test comme étant incomplet ou pas encore implémenté. PHPUnit_Framework_IncompleteTest est une interface de marquage pour signaler une exception qui est levée par une méthode de test comme résultat d'un test incomplet ou actuellement pas implémenté. PHPUnit_Framework_IncompleteTestError est l'implémentation standard de cette interface. Exemple 9.1, « Signaler un test comme incomplet » montre une classe de cas de tests, ExempleDeTest, qui contient une unique méthode de test, method, testSomething(). En appelant la méthode pratique markTestIncomplete() (qui lève automatiquement une exception PHPUnit_Framework_IncompleteTestError) dans la méthode de test, nous marquons le test comme étant incomplet. Exemple 9.1. Signaler un test comme incomplet <?php class ExempleDeTest extends PHPUnit_Framework_TestCase { public function testeQuelquechose() { // Facultatif: testez tout ce que vous voulez ici. $this->assertTrue(TRUE, 'Ceci devrait toujours fonctionner.'); // Cesser ici et marquer ce test comme incomplet. $this->markTestIncomplete( 'Ce test n'a pas encore été implémenté.' ); } } ?> Un test incomplet est signalé par un I sur la sortie écran du lanceur de test en ligne de commandes PHPUnit, comme montré dans l'exemple suivant. PHPUnit 3.7.0 by Sebastian Bergmann. I Tests incomplets et sautés 104 Time: 0 seconds, Memory: 3.75Mb There was 1 incomplete test: 1) ExempleDeTest::testQuelquechose This test has not been implemented yet. /home/sb/ExempleDeTest.php:12 OK, but incomplete or skipped tests! Tests: 1, Assertions: 1, Incomplete: 1.phpunit --verbose ExempleDeTest PHPUnit 3.7.0 by Sebastian Bergmann. I Time: 0 seconds, Memory: 3.75Mb There was 1 incomplete test: 1) ExempleDeTest::testQuelquechose This test has not been implemented yet. /home/sb/ExempleDeTest.php:12 OK, but incomplete or skipped tests! Tests: 1, Assertions: 1, Incomplete: 1. Tableau 9.1, « API pour les tests incomplets » montre l'API pour marquer des tests comme incomplets. Tableau 9.1. API pour les tests incomplets Méthode Signification void markTestIncomplete() Marque le test courant comme incomplet. void markTestIncomplete(string $message) Marque le test courant comme incomplet en utilisant $message comme message d'explication. Sauter des tests Tous les tests ne peuvent pas être exécutés dans tous les environnements. Considérez, par exemple, une couche d'abstraction de base de données qui possède différents pilotes pour les différents systèmes de base de données qu'elle gère. Les tests pour le pilote MySQL ne peuvent bien sûr être exécutés que si un serveur MySQL est disponible. Exemple 9.2, « Sauter un test » montre une classe de cas de tests, DatabaseTest, qui contient une méthode de tests testConnection(). Dans la méthode canevas setUp() de la classe du cas de test, nous pouvons contrôler si l'extension MySQLi est disponible et utiliser la méthode markTestSkipped() pour sauter le test si ce n'est pas le cas. Exemple 9.2. Sauter un test <?php class DatabaseTest extends PHPUnit_Framework_TestCase { protected function setUp() { if (!extension_loaded('mysqli')) { $this->markTestSkipped( 'L\'extension MySQLi n\'est pas disponible.' ); } } Tests incomplets et sautés 105 public function testConnection() { // ... } } ?> Un test qui a été sauté est signalé par un S dans la sortie écran du lanceur de tests en ligne de commande PHPUnit, comme montré dans l'exemple suivant. PHPUnit 3.7.0 by Sebastian Bergmann. S Time: 0 seconds, Memory: 3.75Mb There was 1 skipped test: 1) DatabaseTest::testConnection The MySQLi extension is not available. /home/sb/DatabaseTest.php:9 OK, but incomplete or skipped tests! Tests: 1, Assertions: 0, Skipped: 1.phpunit --verbose DatabaseTest PHPUnit 3.7.0 by Sebastian Bergmann. S Time: 0 seconds, Memory: 3.75Mb There was 1 skipped test: 1) DatabaseTest::testConnection The MySQLi extension is not available. /home/sb/DatabaseTest.php:9 OK, but incomplete or skipped tests! Tests: 1, Assertions: 0, Skipped: 1. Tableau 9.2, « API pour sauter des tests » montre l'API pour sauter des tests. Tableau 9.2. API pour sauter des tests Méthode Signification void markTestSkipped() Marque le test courant comme sauté. void markTestSkipped(string $message) Marque le test courant comme étant sauté en utilisant $message comme message d'explication. Sauter des tests en utilisant requires En plus des méthodes ci-dessus, il est également possible d'utiliser l'annotation requires pour exprimer les préconditions communes pour un cas de test. Tableau 9.3. Usages possibles de requires Type Valeurs possibles Exemple Autre exemple PHP Tout identifiant de version PHP requires PHP 5.3.3 requires PHP 5.4-dev Tests incomplets et sautés 106 Type Valeurs possibles Exemple Autre exemple PHPUnit Tout identifiant de version PHPUnit requires PHPUnit 3.6.3 requires PHPUnit 3.7 function Tout paramètre valide pour function_exists [http://php.net/ function_exists] requires function imap_open requires function ReflectionMethod::setAccessible extension Tout nom d'extension requires extension mysqli requires extension curl Exemple 9.3. Sauter des cas de tests en utilisant requires <?php /** * requires extension mysqli */ class DatabaseTest extends PHPUnit_Framework_TestCase { /** * requires PHP 5.3 */ public function testConnection() { // Test qui nécessite l'extension mysqli et PHP >= 5.3 } // ... Tous les autres tests nécessitent l'extension mysqli } ?> Si vous utilisez une syntaxe qui ne compile pas avec une version données de PHP, regardez dans la configuration xml pour les inclusions dépendant de la version dans la section intitulée « Série de tests » 107 Chapitre 10. Doublure de test Gerard Meszaros introduit le concept de doublure de test dans [Meszaros2007] comme ceci: Parfois il est juste parfaitement difficile de tester un système en cours de test (SCT) parce qu'il dépend d'autres composants qui ne peuvent pas être utilisés dans l'environnement de test. Ceci peut provenir du fait qu'ils ne sont pas disponibles, qu'ils ne retournent pas les résultats nécessaires pour les tests ou parce que les exécuter pourrait avoir des effets de bord indésirables. Dans d'autres cas, notre stratégie de test nécessite que nous ayons plus de contrôle ou de visibilité sur le comportement interne du SCT. Quand nous écrivons un test dans lequel nous ne pouvons pas (ou ne voulons pas) utiliser un composant réel dont on dépend (depended-on component ou DOC), nous pouvons le remplacer avec une doublure de test. La doublure de test ne se comporte pas exactement comme un vrai DOC; elle a simplement à fournir la même API que le composant réel de telle sorte que le système testé pense qu'il s'agit du vrai ! ?Gerard Meszaros La méthode getMock($nomClasse) fournit par PHPUnit peut être utilisée dans un test pour générer automatiquement un objet qui peut agir comme une doublure de test pour une classe originelle indiquée. Cette doublure de test peut être utilisée dans tous les contextes où la classe originelle est attendue. Par défaut, toutes les méthodes de la classe originelle sont remplacées par une implémentation fictive qui se contente de retourner NULL (sans appeler la méthode originelle). En utilisant la méthode will($this->returnValue()) par exemple, vous pouvez configurer ces implémentations fictives pour retourner une valeur donnée quand elles sont appelées. Limitations Merci de noter que les méthodes final, private et static ne peuvent pas être remplacées par un bouchon (stub) ou un simulacre (mock). Elles seront ignorées par la fonction de doublure de test de PHPUnit et conserveront leur comportement initial. Bouchons La pratique consistant à remplacer un objet par une doublure de test qui retourne (de façon facultative) des valeurs de retour configurées est appelée bouchonnage. Vous pouvez utiliser un bouchon pour "remplacer un composant réel dont dépend le système testé de telle façon que le test possède un point de contrôle sur les entrées indirectes dans le SCT. Ceci permet au test de forcer le SCT à utiliser des chemins qu'il n'aurait pas emprunté autrement". Exemple 10.2, « Bouchonner un appel de méthode pour retourner une valeur fixée » montre comment la méthode de bouchonnage appelle et configure des valeurs de retour. Nous utilisons d'abord la méthode getMock() qui est fournie par la classe PHPUnit_Framework_TestCase pour configurer un objet bouchon qui ressemble à un objet de UneClasse (Exemple 10.1, « La classe que nous voulons bouchonner »). Ensuite nous utilisons l'interface souple [http://martinfowler.com/ bliki/FluentInterface.html] que PHPUnit fournit pour indiquer le comportement de ce bouchon. En essence, cela signifie que vous n'avez pas besoin de créer plusieurs objets temporaires et les relier ensemble ensuite. Au lieu de cela, vous chaînez les appels de méthode comme montré dans l'exemple. Ceci amène à un code plus lisible et "souple". Exemple 10.1. La classe que nous voulons bouchonner <?php class UneClasse { Doublure de test 108 public function faireQuelquechose() { // Faire quelque chose. } } ?> Exemple 10.2. Bouchonner un appel de méthode pour retourner une valeur fixée <?php require_once 'UneClasse.php'; class BouchonTest extends PHPUnit_Framework_TestCase { public function testBouchon() { // Créer un bouchon pour la classe UneClasse. $bouchon = $this->getMock('UneClasse'); // Configurer le bouchon. $bouchon->expects($this->any()) ->method('faireQuelquechose') ->will($this->returnValue('foo')); // Appeler $bouchon->faireQuelquechose() va maintenant retourner // 'foo'. $this->assertEquals('foo', $bouchon->faireQuelquechose()); } } ?> "Derrière la scène", PHPUnit génère automatiquement une nouvelle classe qui implémente le comportement souhaité quand la méthode getMock() est utilisée. La classe doublure de test peut être configurée via des paramètres optionnels de la méthode getMock(). ? Par défaut, toutes les méthodes d'une classe données sont remplacées par une doublure de test qui retourne simplement NULL à moins qu'une valeur de retour ne soit configurée en utilisant will($this->returnValue()), par exemple. ? Quand le deuxième paramètre (facultatif) est fourni, seules les méthodes dont les noms sont dans le tableau sont remplacées par une doublure de test configurable. Le comportement des autres méthodes n'est pas modifié. ? Le troisième paramètre (facultatif) peut contenir un tableau de paramètre qui est passé dans le constructeur de la classe originelle (qui n'est pas remplacé par une implémentation fictive par défaut). ? Le quatrième paramètre (facultatif) peut être utilisé pour indiquer un nom de classe pour la classe de doublure de test générée. ? Le cinquième paramètre (facultatif) peut être utilisée pour désactiver l'appel du constructeur de la classe originelle. ? Le sixième paramètre (facultatif) peut être utilisé pour désactiver l'appel au constructeur du clone de la classe originelle. ? Le septième paramètre (facultatif) peut être utilisé pour désactiver __autoload() lors de la génération de la classe de doublure de test. Alternativement, l'API Mock Builder peut être utilisé pour configurer la classe de doublure de test générée. Exemple 10.3, « Utiliser l'API Mock Builder pour configurer la classe de doublure de test Doublure de test 109 générée. » montre un exemple. Ici, il y a une liste de méthodes qui peuvent être utilisées avec l'interface de Mock Builder: ? setMethods(array $methodes) peut être appelée sur l'objet Mock Builder pour indiquer les méthodes qui doivent être remplacées par une doublure de test configurable. Le comportement des autres méthodes n'est pas modifié. ? setConstructorArgs(array $parametres) peut être appelé pour fournir un paramètre tableau qui est passé au constructeur de la classe originelle (qui n'est pas remplacé par une implémentation fictive par défaut). ? setMockClassName($nom) peut être utilisée pour indiquer un nom de classe pour la classe de doublure de test générée. ? disableOriginalConstructor() peut être utilisé pour désactiver l'appel au constructeur de la classe originelle. ? disableOriginalClone() peut être utilisé pour désactiver l'appel au constructeur clone de la classe originelle. ? disableAutoload() peut être utilisée pour désactiver __autoload() lors de la génération de la classe de doublure de test. Exemple 10.3. Utiliser l'API Mock Builder pour configurer la classe de doublure de test générée. <?php require_once 'UneClasse.php'; class BouchonTest extends PHPUnit_Framework_TestCase { public function testBouchon() { // Créer un bouchon pour la classe UneClasse. $bouchon = $this->getMockBuilder('UneClasse') ->disableOriginalConstructor() ->getMock(); // Configure le bouchon. $bouchon->expects($this->any()) ->method('faireQuelquechose') ->will($this->returnValue('foo')); // Appeler $bouchon->faireQuelquechose() retournera maintenant // 'foo'. $this->assertEquals('foo', $bouchon->faireQuelquechose()); } } ?> Parfois vous voulez renvoyer l'un des paramètres d'un appel de méthode (non modifié) comme résultat d'un appel méthode bouchon. Exemple 10.4, « Bouchonner un appel de méthode pour renvoyer un des paramètres » montre comment vous pouvez obtenir ceci en utilisant returnArgument() à la place de returnValue(). Exemple 10.4. Bouchonner un appel de méthode pour renvoyer un des paramètres <?php require_once 'UneClasse.php'; Doublure de test 110 class BouchonTest extends PHPUnit_Framework_TestCase { public function testReturnArgumentBouchon() { // Créer un bouchon pour la classe UneClasse. $bouchon = $this->getMock('UneClasse'); // Configurer le bouchon. $bouchon->expects($this->any()) ->method('faireQuelquechose') ->will($this->returnArgument(0)); // $bouchon->faireQuelquechose('foo') retourne 'foo' $this->assertEquals('foo', $bouchon->faireQuelquechose('foo')); // $bouchon->faireQuelquechose('bar') retourne 'bar' $this->assertEquals('bar', $bouchon->faireQuelquechose('bar')); } } ?> Quand on teste interface souple, il est parfois utile que la méthode bouchon retourne une référence à l'objet bouchon. Exemple 10.5, « Bouchonner un appel de méthode pour renvoyer une référence de l'objet bouchon. » illustre comment vous pouvez utiliser returnSelf() pour accomplir cela. Exemple 10.5. Bouchonner un appel de méthode pour renvoyer une référence de l'objet bouchon. <?php require_once 'UneClasse.php'; class BouchonTest extends PHPUnit_Framework_TestCase { public function testReturnSelf() { // Créer un bouchon pour la classe UneClasse. $bouchon = $this->getMock('UneClasse'); // Configurer le bouchon. $bouchon->expects($this->any()) ->method('faireQuelquechose') ->will($this->returnSelf()); // $bouchon->faireQuelquechose() retourne $bouchon $this->assertSame($bouchon, $bouchon->faireQuelquechose()); } } ?> Parfois, une méthode bouchon doit retourner différentes valeurs selon une liste prédéfinie d'arguments. Vous pouvez utiliser returnValueMap() pour créer un mappage qui associe des paramètres aux valeurs de retour correspondantes. Voir Exemple 10.6, « Bouchonner un appel de méthode pour retourner la valeur à partir d'un mappage » pour un exemple. Exemple 10.6. Bouchonner un appel de méthode pour retourner la valeur à partir d'un mappage <?php require_once 'UneClasse.php'; Doublure de test 111 class BouchonTest extends PHPUnit_Framework_TestCase { public function testReturnValueMapBouchon() { // Créer un bouchon pour la classe UneClasse. $bouchon = $this->getMock('UneClasse'); // Créer un mappage des arguments // et des valeurs de retour. $map = array( array('a', 'b', 'c', 'd'), array('e', 'f', 'g', 'h') ); // Configurer le bouchon. $bouchon->expects($this->any()) ->method('faireQuelquechose') ->will($this->returnValueMap($map)); // $bouchon->faireQuelquechose() retourne // différentes valeurs selon les paramètres // fournis. $this->assertEquals('d', $bouchon->faireQuelquechose('a', 'b', 'c')); $this->assertEquals('h', $bouchon->faireQuelquechose('e', 'f', 'g')); } } ?> Quand l'appel méthode bouchonné doit retourner une valeur calculée au lieu d'une valeur fixée (voir returnValue()) ou un paramètre (non modifié) (voir returnArgument()), vous pouvez utiliser returnCallback() pour que la méthode retourne le résultat d'une fonction ou méthode de rappel. Voir Exemple 10.7, « Bouchonner un appel de méthode pour retourner une valeur à partir d'un rappel » pour un exemple. Exemple 10.7. Bouchonner un appel de méthode pour retourner une valeur à partir d'un rappel <?php require_once 'UneClasse.php'; class BouchonTest extends PHPUnit_Framework_TestCase { public function testReturnCallbackBouchon() { // Créer un bouchon pour la classe UneClasse. $bouchon = $this->getMock('UneClasse'); // Configurer le bouchon. $bouchon->expects($this->any()) ->method('faireQuelquechose') ->will($this->returnCallback('str_rot13')); // $bouchon->faireQuelquechose($argument) retourne str_rot13($argument) $this->assertEquals('fbzrguvat', $bouchon->faireQuelquechose('quelqueChose')); } } ?> Une alternative plus simple pour configurer une méthode de rappel peut consister à indiquer une liste de valeurs désirées. Vous pouvez faire ceci avec la méthode onConsecutiveCalls(). Voir Exemple 10.8, « Bouchonner un appel de méthode pour retourner une liste de valeurs dans l'ordre indiqué » pour un exemple. Doublure de test 112 Exemple 10.8. Bouchonner un appel de méthode pour retourner une liste de valeurs dans l'ordre indiqué <?php require_once 'UneClasse.php'; class BouchonTest extends PHPUnit_Framework_TestCase { public function testOnConsecutiveCallsBouchon() { // Créer un bouchon pour la classe UneClasse. $bouchon = $this->getMock('UneClasse'); // Configurer le bouchon. $bouchon->expects($this->any()) ->method('faireQuelquechose') ->will($this->onConsecutiveCalls(2, 3, 5, 7)); // $bouchon->faireQuelquechose() retourne une valeur différente à chaque fois $this->assertEquals(2, $bouchon->faireQuelquechose()); $this->assertEquals(3, $bouchon->faireQuelquechose()); $this->assertEquals(5, $bouchon->faireQuelquechose()); } } ?> Au lieu de retourner une valeur, une méthode bouchon peut également lever une exception. Exemple 10.9, « Bouchonner un appel de méthode pour lever une exception » montre comme utiliser throwException() pour faire cela. Exemple 10.9. Bouchonner un appel de méthode pour lever une exception <?php require_once 'UneClasse.php'; class BouchonTest extends PHPUnit_Framework_TestCase { public function testThrowExceptionBouchon() { // Créer un bouchon pour la classe UneClasse. $bouchon = $this->getMock('UneClasse'); // Configurer le bouchon. $bouchon->expects($this->any()) ->method('faireQuelquechose') ->will($this->throwException(new Exception)); // $bouchon->faireQuelquechose() lance l'Exception $bouchon->faireQuelquechose(); } } ?> Alternativement, vous pouvez écrire le bouchon vous-même et améliorer votre conception ce-faisant. Des ressources largement utilisées sont accédées via une unique façade, de telle sorte que vous pouvez facilement remplacer la ressource avec le bouchon. Par exemple, au lieu d'avoir des appels directs à la base de données éparpillés dans tout le code, vous avez un unique objet Database, une implémentation de l'interface IDatabase. Ensuite, vous pouvez créer une implémentation bouchon de IDatabase et l'utiliser pour vos tests. Vous pouvez même créer une option pour lancer les tests dans la base de données bouchon ou la base de données réelle, de telle sorte que vous pouvez utiliser Doublure de test 113 vos tests à la fois pour tester localement pendant le développement et en intégration avec la vraie base de données. Les fonctionnalités qui nécessitent d'être bouchonnées tendent à se regrouper dans le même objet, améliorant la cohésion. En représentant la fonctionnalité avec une unique interface cohérente, vous réduisez le couplage avec le reste du système. Objets simulacres (Mock Objects) La pratique consistant à remplacer un objet avec une doublure de test qui vérifie des attentes, par exemple en faisant l'assertion qu'une méthode a été appelée, est appelée simulacre. Vous pouvez utiliser un objet simulacre "comme un point d'observation qui est utilisé pour vérifier les sorties indirectes du système quand il est testé. Typiquement, le simulacre inclut également la fonctionnalité d'un bouchon de test, en ce sens qu'il doit retourner les valeurs du système testé s'il n'a pas déjà fait échouer les tests mais l'accent est mis sur la vérification des sorties indirectes. Ainsi, un simulacre est un beaucoup plus qu'un simple bouchon avec des assertions; il est utilisé d'une manière fondamentalement différente". Voici un exemple: supposons que vous voulez tester que la méthode correcte, update() dans notre exemple, est appelée d'un objet qui observe un autre objet. Exemple 10.10, « Les classes Sujet et Observateur qui sont une partie du système testé » illustre le code pour les classes Sujet et Observateur qui sont une partie du système testé (SUT). Exemple 10.10. Les classes Sujet et Observateur qui sont une partie du système testé <?php class Sujet { protected $observateurs = array(); public function attache(Observateur $observateur) { $this->observateurs[] = $observateur; } public function faireQuelquechose() { // Faire quelque chose. // ... // Avertir les observateurs que nous faisons quelque chose. $this->notify('quelque chose'); } public function faireQuelquechoseMal() { foreach ($this->observateurs as $observateur) { $observateur->reportError(42, 'Quelque chose de mal est arrivé', $this); } } protected function notify($paramètre) { foreach ($this->observateurs as $observateur) { $observateur->update($paramètre); } } Doublure de test 114 // Autres méthodes. } class Observateur { public function update($paramètre) { // Faire quelque chose. } public function reportError($codeErreur, $messageErreur, Sujet $sujet) { // Faire quelque chose } // Autres méthodes. } ?> Exemple 10.11, « Tester qu'une méthode est appelée une fois et avec un paramètre indiqué » illustre comment utiliser un simulacre pour tester l'interaction entre les objets Sujet et Observateur. Nous utilisons d'abord la méthode getMock() qui est fournie par la classe PHPUnit_Framework_TestCase pour configurer un simulacre pour l'Observateur. Puisque nous donnons un tableau comme second paramètre (facultatif) pour la méthode getMock(), seule la méthode update() de la classe Observateur est remplacée par une implémentation d'un simulacre. Exemple 10.11. Tester qu'une méthode est appelée une fois et avec un paramètre indiqué <?php require_once 'Sujet.php'; class SujetTest extends PHPUnit_Framework_TestCase { public function testLesObservateursSontMisAJour() { // Créer un simulacre pour la classe Observateur, // ne touchant que la méthode update(). $observateur = $this->getMock('Observateur', array('update')); // Configurer l'attente de la méthode update() // d'être appelée une seule fois et avec la chaîne 'quelquechose' // comme paramètre. $observateur->expects($this->once()) ->method('update') ->with($this->equalTo('quelque chose')); // Créer un objet Sujet et y attacher l'objet Observateur // simulé $sujet = new Sujet; $sujet->attache($observateur); // Appeler la méthode faireQuelquechose() sur l'objet $sujet // que nous attendons voir appeler la méthode update() de l'objet // simulé Observateur avec la chaîne 'quelqueChose'. $sujet->faireQuelquechose(); } } ?> Doublure de test 115 La méthode with() peut prendre n'importe quel nombre de paramètres, correspondant au nombre de paramètres des méthodes étant simulées. Vous pouvez indiquer des contraintes plus avancées sur les paramètres de méthode qu'une simple correspondance. Exemple 10.12. Tester qu'une méthode est appelée avec un nombre de paramètres contraints de différentes manières <?php class SubjectTest extends PHPUnit_Framework_TestCase { public function testRapportErreur() { // Créer un simulacre pour la classe Observateur, en simulant // la méthode rapportErreur() $observateur = $this->getMock('Observateur', array('rapportErreur')); $observateur->expects($this->once()) ->method('rapportErreur') ->with($this->greaterThan(0), $this->stringContains('Quelquechose'), $this->anything()); $sujet = new Subject; $sujet->attach($observateur); // La méthode faireQuelquechoseDeMal doit rapporter une erreur à l'observateur // via la méthode rapportErreur() $sujet->faireQuelquechoseDeMal(); } } ?> Tableau 4.3, « Contraintes » montre les contraintes qui peuvent être appliquées aux paramètres de méthode et Tableau 10.1, « Matchers » montre les matchers qui sont disponibles pour indiquer le nombre d' invocations. Tableau 10.1. Matchers Matcher Signification PHPUnit_Framework_MockObject_ Matcher_AnyInvokedCount any() Retourne un matcher qui correspond quand la méthode pour laquelle il est évalué est exécutée zéro ou davantage de fois. PHPUnit_Framework_MockObject_ Matcher_InvokedCount never() Retourne un matcher qui correspond quand la méthode pour laquelle il est évalué n'est jamais exécutée. PHPUnit_Framework_MockObject_ Matcher_InvokedAtLeastOnce atLeastOnce() Retourne un matcher qui correspond quand la méthode pour laquelle il est évalué est exécutée au moins une fois. PHPUnit_Framework_MockObject_ Matcher_InvokedCount once() Retourne un matcher qui correspond quand la méthode pour laquelle il est évalué est exécutée exactement une fois. PHPUnit_Framework_MockObject_ Matcher_InvokedCount exactly(int $nombre) Retourne un matcher qui correspond quand la méthode pour laquelle il est évalué est exécutée exactement $nombre fois. PHPUnit_Framework_MockObject_ Matcher_InvokedAtIndex at(int $index) Retourne un matcher qui correspond quand la méthode pour laquelle il est évalué est invoquée pour l'$index spécifié. Doublure de test 116 La méthode getMockForAbstractClass() retourne un simulacre pour une classe abstraite. Toutes les méthodes abstraites d'une classe simulacre donnée sont simulées. Ceci permet de tester les méthodes concrètes d'une classe abstraite. Exemple 10.13. Tester les méthodes concrêtes d'une classe abstraite <?php abstract class ClasseAbstraite { public function methodeConcrete() { return $this->methodeAbstraite(); } public abstract function methodeAbstraite(); } class ClasseAbstraiteTest extends PHPUnit_Framework_TestCase { public function testConcreteMethod() { $stub = $this->getMockForAbstractClass('ClasseAbstraite'); $stub->expects($this->any()) ->method('methodeAbstraite') ->will($this->returnValue(TRUE)); $this->assertTrue($stub->methodeConcrete()); } } ?> Bouchon et simulacre pour Web Services Quand votre application interagit avec un web service, vous voulez le tester sans vraiment interagir avec le web service. Pour rendre facile la création de bouchon ou de simulacre de web services, getMockFromWsdl() peut être utilisée de la même façon que getMock() (voir plus haut). La seule différence est que getMockFromWsdl() retourne un bouchon ou un simulacre basé sur la description en WSDL d'un web service tandis que getMock() retourne un bouchon ou un simulacre basé sur une classe ou une interface PHP. Exemple 10.14, « Bouchonner un web service » montre comment getMockFromWsdl() peut être utilisé pour faire un bouchon, par exemple, d'un web service décrit dans GoogleSearch.wsdl. Exemple 10.14. Bouchonner un web service <?php class GoogleTest extends PHPUnit_Framework_TestCase { public function testSearch() { $googleSearch = $this->getMockFromWsdl( 'GoogleSearch.wsdl', 'GoogleSearch' ); $directoryCategory = new StdClass; $directoryCategory->fullViewableName = ''; $directoryCategory->specialEncoding = ''; $element = new StdClass; $element->summary = ''; $element->URL = 'http://www.phpunit.de/'; Doublure de test 117 $element->snippet = '...'; $element->title = '<b>PHPUnit</b>'; $element->cachedSize = '11k'; $element->relatedInformationPresent = TRUE; $element->hostName = 'www.phpunit.de'; $element->directoryCategory = $directoryCategory; $element->directoryTitle = ''; $result = new StdClass; $result->documentFiltering = FALSE; $result->searchComments = ''; $result->estimatedTotalResultsCount = 378000; $result->estimateIsExact = FALSE; $result->resultElements = array($element); $result->searchQuery = 'PHPUnit'; $result->startIndex = 1; $result->endIndex = 1; $result->searchTips = ''; $result->directoryCategories = array(); $result->searchTime = 0.248822; $googleSearch->expects($this->any()) ->method('doGoogleSearch') ->will($this->returnValue($result)); /** * $googleSearch->doGoogleSearch() va maintenant retourner un result bouchon et * la méthode doGoogleSearch() du web service ne sera pas invoquée. */ $this->assertEquals( $result, $googleSearch->doGoogleSearch( '00000000000000000000000000000000', 'PHPUnit', 0, 1, FALSE, '', FALSE, '', '', '' ) ); } } ?> Simuler le système de fichiers vfsStream [http://code.google.com/p/bovigo/wiki/vfsStream] est un encapsuleur de flux [http:// www.php.net/streams] pour un système de fichiers virtuel [http://en.wikipedia.org/wiki/ Virtual_file_system] qui peut s'avérer utile dans des tests unitaires pour simuler le vrai système de fichiers. Pour installer vfsStream, le canal PEAR (pear.php-tools.net) qui est utilisé pour sa distribution doit être enregistré dans l'environnement local PEAR: pear channel-discover pear.php-tools.net Ceci ne doit être fait qu'une seule fois. Maintenant, l'installeur PEAR peut être utilisé pour installer vfsStream. Doublure de test 118 pear install pat/vfsStream-beta Exemple 10.15, « Une classe qui interagit avec le système de fichiers » montre une classe qui interagit avec le système de fichiers. Exemple 10.15. Une classe qui interagit avec le système de fichiers <?php class Exemple { protected $id; protected $repertoire; public function __construct($id) { $this->id = $id; } public function setRepertoire($repertoire) { $this->repertoire = $repertoire . DIRECTORY_SEPARATOR . $this->id; if (!file_exists($this->repertoire)) { mkdir($this->repertoire, 0700, TRUE); } } }?> Sans un système de fichiers virtuel tel que vfsStream, nous ne pouvons pas tester la méthode setDirectory() en isolation des influences extérieures (voir Exemple 10.16, « Tester une classe qui interagoit avec le système de fichiers »). Exemple 10.16. Tester une classe qui interagoit avec le système de fichiers <?php require_once 'Exemple.php'; class ExempleTest extends PHPUnit_Framework_TestCase { protected function setUp() { if (file_exists(dirname(__FILE__) . '/id')) { rmdir(dirname(__FILE__) . '/id'); } } public function testReprtoireEstCree() { $example = new Exemple('id'); $this->assertFalse(file_exists(dirname(__FILE__) . '/id')); $example->setRepertoire(dirname(__FILE__)); $this->assertTrue(file_exists(dirname(__FILE__) . '/id')); } protected function tearDown() { if (file_exists(dirname(__FILE__) . '/id')) { rmdir(dirname(__FILE__) . '/id'); } } } Doublure de test 119 ?> L'approche précédente possède plusieurs inconvénients : ? Comme avec les ressources externes, il peut y a voir des problèmes intermittents avec le système de fichiers. Ceci rend les tests qui interagissent avec lui peu fiables. ? Dans les méthodes setUp() et tearDown(), nous avons à nous assurer que le répertoire n'existe pas avant et après le test. ? Si l'exécution du test s'achève avant que la méthode tearDown() n'ait été appelée, le répertoire va rester dans le système de fichiers. Exemple 10.17, « Simuler le système de fichiers dans un test pour une classe qui interagit avec le système de fichiers » montre comment vfsStream peut être utilisé pour simuler le système de fichiers dans un test pour une classe qui interagit avec le système de fichiers. Exemple 10.17. Simuler le système de fichiers dans un test pour une classe qui interagit avec le système de fichiers <?php require_once 'vfsStream/vfsStream.php'; require_once 'Exemple.php'; class ExempleTest extends PHPUnit_Framework_TestCase { public function setUp() { vfsStreamWrapper::register(); vfsStreamWrapper::setRoot(new vfsStreamDirectory('exempleRepertoire')); } public function testRepertoireEstCree() { $exemple = new Exemple('id'); $this->assertFalse(vfsStreamWrapper::getRoot()->hasChild('id')); $exemple->setRepertoire(vfsStream::url('exempleRepertoire')); $this->assertTrue(vfsStreamWrapper::getRoot()->hasChild('id')); } } ?> Ceci présente plusieurs avantages : ? Le test lui-même est plus concis. ? vfsStream donne au développeur du test le plein contrôle sur la façon dont le code testé voit l'environnement du système de fichiers. ? Puisque les opérations du système de fichiers n'opèrent plus sur le système de fichiers réel, les opérations de nettoyage dans la méthode tearDown() ne sont plus nécessaires. 120 Chapitre 11. Pratiques de test Vous pouvez toujours écrire davantage de tests. Cependant, vous vous rendrez rapidement compte que seule une fraction des tests auxquels vous pensez est réellement utile. Ce que vous voulez, c'est écrire des tests qui échouent même si vous pensez qu'ils devraient fonctionner, ou des tests qui réussissent même si vous pensez qu'ils devraient échouer. Une autre façon d'envisager cela consiste à le faire en terme de coûts/bénéfices. Vous voulez écrire des tests qui vous paient en retour avec des informations. ?Erich Gamma Pendant le développement Lorsque vous avez besoin de modifier la structure interne d'un logiciel sur lequel vous êtes en train de travailler pour la rendre plus facile à comprendre et moins chère à modifier sans modifier son comportement visible, une suite de tests est inestimable pour appliquer de façon sure ces refactorings [http://martinfowler.com/bliki/DefinitionOfRefactoring.html] comme on les appelle. Sans cela, vous ne pourrez pas vous détecter que vous avez cassé le système en réalisant la restructuration. Les conditions suivantes vous aideront à améliorer le code et la conception de votre projet, tant que vous utiliserez des tests unitaires pour vérifier que les étapes de transformation dues au refactoring préservent bien le comportement et n'introduisent pas d'erreurs : 1. Tous les tests unitaires fonctionnent correctement. 2. Le code transmet des principes de conception. 3. Le code ne contient pas de redondances. 4. Le code contient le nombre minimal de classes et de méthodes. Quand vous avez besoin d'ajouter de nouvelles fonctionnalités au système, écrivez d'abord les tests. Puis, vous aurez terminé quand les tests s'exécuteront. Cette pratique sera discutée en détail dans le prochain chapitre. Pendant le débogage Quand vous recevez un rapport de bug, vous pourriez être tenté de le corriger aussi vite que possible. L'expérience prouve que cette impulsion ne vous rendra pas service : il est probable que ce défaut en provoque un autre. Vous pouvez maîtriser cette impulsion en suivant ce qui suit : 1. Vérifiez que vous pouvez reproduire le défaut. 2. Trouvez la démonstration la plus ciblée possible du défaut dans le code. Par exemple, si un nombre s'affiche incorrectement dans une sortie écran, trouvez l'objet qui calcule ce nombre. 3. Ecrivez un test automatisé qui échoue maintenant mais qui réussira quand le défaut sera corrigé. 4. Corrigez le défaut. Trouver le moyen fiable le plus ciblé pour reproduire le défaut vous offre l'opportunité de vraiment en examiner la cause. Le test que vous écrivez va améliorer les chances que lorsque vous corrigerez le défaut, vous le fassiez vraiment du fait que le nouveau test réduit la probabilité de défaire cette correction lors de futures modifications du code. Tous les tests écrits avant réduisent la probabilité de causer par inadvertance un problème différent. Pratiques de test 121 Le test unitaire offre de nombreux avantages : ? Tester conforte les auteurs de code et les relecteurs dans le fait que les correctifs produisent des résultats corrects.. ? Créer des cas de tests est un bon moyen de conduire les développeurs à découvrir des cas limites. ? Tester fournit un bon moyen de capturer les régressions rapidement et de s'assurer qu'elles ne seront pas répétées deux fois. ? Les tests unitaires fournissent des exemples fonctionnels de la façon d'utiliser une API et peuvent aider significativement à l'effort de documentation. Globalement, le test unitaire intégré minimise les coûts et les risques de toute modification individuelle. Il permettra au projet de mener [...] des améliorations majeures d'architecture [...] rapidement et avec confiance. ?Benjamin Smedberg 122 Chapitre 12. Développement dirigé par les tests Les tests unitaires sont une partie vitale pour plusieurs pratiques et processus de développement logiciel tel que la programmation en testant d'abord, l'Extreme Programming [http:// en.wikipedia.org/wiki/Extreme_Programming], et le développement dirigé par les tests [http:// en.wikipedia.org/wiki/Test-driven_development]. Ils permettent également la conception par contrat [http://en.wikipedia.org/wiki/Design_by_Contract] dans des langages de programmation qui ne supportent pas cette méthodologie par construction de langage. Vous pouvez utiliser PHPUnit pour écrire des tests une fois que vous avez fait la programmation. Cependant, plus tôt un test est écrit après qu'une erreur a été introduite, plus le test a de la valeur. Ainsi, au lieu d'écrire des tests des mois après que le code est "achevé", nous pouvons écrire des tests quelques jours, heures ou minutes après la possible introduction d'un défaut. Pourquoi s'arrêter là ? Pourquoi ne pas écrire les tests un peu avant la possible introduction d'un défaut ? La programmation en testant d'abord, qui est une partie de l'Extreme Programming et le développement dirigé par les tests, sont bâtis sur cette idée et la poussent à l'extrême. Grâce à la puissance de calcul actuelle, nous avons l'opportunité de lancer des milliers de tests des milliers de fois par jour. Nous pouvons utiliser les retours de tous ces tests pour programmer par petites étapes, chacune d'elles apportant avec elle l'assurance d'un nouveau test automatisé s'ajoutant à tous les tests venus précédemment. Les tests sont comme des pitons, vous assurant que, quoi qu'il arrive, une fois que vous avez progressé, vous ne pouvez pas retomber plus bas. Quand vous écrivez le test la première fois, il ne peut pas être exécuté, car vous faites appel à des objets et des méthodes qui n'ont pas encore été programmés. Ceci peut sembler étrange au premier abord, mais après un moment, vous aurez l'habitude de procéder ainsi. Pensez à la programmation en testant d'abord comme à une approche pragmatique pour suivre le principe de programmation orientée objet consistant à programmer une interface au lieu de programmer une implémentation : quand vous écrivez le test, vous pensez à l'interface de l'objet que vous êtes en train de tester - ce à quoi ressemble cet objet vu de l'extérieur. Quand vous faites en sorte que le test fonctionne vraiment, vous réfléchissez en terme de pure implémentation. L'interface est déterminée par le test en échec. L'objet du Développement dirigé par les tests [http://en.wikipedia.org/wiki/Test- driven_development] est de rechercher les fonctionnalités dont le logiciel a réellement besoin, plutôt que celles dont le programmeur pense qu'il pourrait probablement avoir besoin. La façon dont il procède semble d'abord contre intuitive, si ce n'est carrément idiote, mais non seulement cela a du sens, mais cela devient également rapidement une façon naturelle et élégante de développer du logiciel. ?Dan North Ce qui suit est forcément une introduction abrégée au développement dirigé par les tests. Vous pouvez approfondir le sujet dans d'autres livres, comme Test-Driven Development [Beck2002] de Kent Beck ou A Practical Guide to Test-Driven Development [Astels2003] de Dave Astels. Exemple du compte bancaire Dans cette section, nous examinerons l'exemple d'une classe qui représente un compte bancaire. Le contrat pour la classe CompteBancaire n'exige pas seulement des méthodes pour obtenir et positionner la balance du compte bancaire, ainsi que des méthodes pour déposer et retirer de l'argent. S'y ajoute les deux conditions suivantes qui doivent être vérifiées : ? La balance initiale du compte bancaire doit être à zéro. ? La balance du compte bancaire ne peut pas devenir négative. Développement dirigé par les tests 123 Nous écrivons les tests pour la classe CompteBancaire avant d'écrire le code de la classe elle-même. Nous utilisons les conditions du contrat comme base pour les tests et nous nommons les méthodes de test en fonction, comme montré dans Exemple 12.1, « Tests pour la classe CompteBancaire ». Exemple 12.1. Tests pour la classe CompteBancaire <?php require_once 'CompteBancaire.php'; class CompteBancaireTest extends PHPUnit_Framework_TestCase { protected $compte_bancaire; protected function setUp() { $this->compte_bancaire = new CompteBancaire; } public function testBalanceEstInitialementAZero() { $this->assertEquals(0, $this->compte_bancaire->getBalance()); } public function testBalanceNePeutPasEtreNegatif() { try { $this->compte_bancaire->retirerArgent(1); } catch (CompteBancaireException $e) { $this->assertEquals(0, $this->compte_bancaire->getBalance()); return; } $this->fail(); } public function testBalanceNePeutPasEtreNegatif2() { try { $this->compte_bancaire->deposerArgent(-1); } catch (CompteBancaireException $e) { $this->assertEquals(0, $this->compte_bancaire->getBalance()); return; } $this->fail(); } } ?> Nous écrivons maintenant le volume minimum de code pour que le premier test, testBalanceEstInitialementAZero(), réussisse. Dans notre exemple, ceci correspond à implémenter la méthode getBalance() de la classe CompteBancaire, comme montré dans Exemple 12.2, « Code nécessaire pour que le test testBalanceEstInitialementAZero() réussisse ». Développement dirigé par les tests 124 Exemple 12.2. Code nécessaire pour que le test testBalanceEstInitialementAZero() réussisse <?php class CompteBancaire { protected $balance = 0; public function getBalance() { return $this->balance; } } ?> Maintenant, le test pour la première condition du contrat réussit, mais les tests pour la seconde condition du contrat échoue car nous n'avons pas implémenté les méthodes que ces tests appellent. PHPUnit 3.7.0 by Sebastian Bergmann. . Fatal error: Call to undefined method CompteBancaire::retirerArgent()phpunit CompteBancai PHPUnit 3.7.0 by Sebastian Bergmann. . Fatal error: Call to undefined method CompteBancaire::retirerArgent() Pour que les tests qui s'assurent que la seconde condition du contrat réussissent, nous devons maintenant implémenter les méthodes retirerArgent(), deposerArgent() et setBalance(), comme montré dans Exemple 12.3, « La classe CompteBancaire complète ». Ces méthodes sont écrites de telle façon qu'elles lèvent une CompteBancaireException quand elles sont appelées avec des valeurs illégales qui violeraient les conditions du contrat. Exemple 12.3. La classe CompteBancaire complète <?php class CompteBancaireException extends Exception { } class CompteBancaire { protected $balance = 0; public function getBalance() { return $this->balance; } protected function setBalance($balance) { if ($balance >= 0) { $this->balance = $balance; } else { throw new CompteBancaireException; } } public function deposerArgent($balance) { $this->setBalance($this->getBalance() + $balance); return $this->getBalance(); Développement dirigé par les tests 125 } public function retirerArgent($balance) { $this->setBalance($this->getBalance() - $balance); return $this->getBalance(); } } ?> Les tests qui assurent que la seconde condition du contrat réussissent maintenant aussi : PHPUnit 3.7.0 by Sebastian Bergmann. ... Time: 0 seconds OK (3 tests, 3 assertions)phpunit CompteBancaireTest PHPUnit 3.7.0 by Sebastian Bergmann. ... Time: 0 seconds OK (3 tests, 3 assertions) Alternativement, vous pouvez utiliser les méthodes statiques de vérification fournies par la classe PHPUnit_Framework_Assert pour écrire les conditions du contrat en tant que vérification en style "conception par contrat", comme montré dans Exemple 12.4, « La classe CompteBancaire avec des vérifications de conception par contrat ». Quand l'une de ces vérifications échoue, une exception PHPUnit_Framework_AssertionFailedError sera levée. Avec cette approche, vous écrivez moins de code pour les contrôles des conditions du contrat et les tests deviennent plus lisibles. Cependant, vous ajoutez une dépendance à l'exécution avec PHPUnit à vos projets. Exemple 12.4. La classe CompteBancaire avec des vérifications de conception par contrat <?php class CompteBancaire { private $balance = 0; public function getBalance() { return $this->balance; } protected function setBalance($balance) { PHPUnit_Framework_Assert::assertTrue($balance >= 0); $this->balance = $balance; } public function deposerArgent($montant) { PHPUnit_Framework_Assert::assertTrue($montant >= 0); Développement dirigé par les tests 126 $this->setBalance($this->getBalance() + $montant); return $this->getBalance(); } public function retirerArgent($montant) { PHPUnit_Framework_Assert::assertTrue($montant >= 0); PHPUnit_Framework_Assert::assertTrue($this->balance >= $montant); $this->setBalance($this->getBalance() - $montant); return $this->getBalance(); } } ?> En écrivant les conditions du contrat dans les tests, nous avons utilisé la conception par contrat pour programmer la classe CompteBancaire. Nous avons alors écrit, suivant l'approche de la programmation en testant d'abord, le code nécessaire pour faire que les tests réussissent. Cependant, nous avons oublié d'écrire les tests qui appellent setBalance(), deposerArgent() et retirerArgent() avec des valeurs valides qui ne violent pas les conditions du contrat. Nous avons besoin d'un moyen pour tester nos tests, ou au moins mesurer leur qualité. Un tel moyen est l'analyse de l'information de couverture de code que nous allons voir. 127 Chapitre 13. Développement dirigé par le comportement Dans [Astels2006], Dave Astels fait le point suivant : ? Extreme Programming [http://en.wikipedia.org/wiki/Extreme_Programming] avait à l'origine comme règle de tester tout ce qui peut éventuellement provoquer un défaut. ? Maintenant, cependant, la pratique du test en Extreme Programming a évolué en Développement dirigé par les tests [http://en.wikipedia.org/wiki/Test-driven_development] (voir Chapitre 12, Développement dirigé par les tests). ? Mais les outils continuent à obliger les développeurs à penser en terme de tests et de vérifications au lieu de spécifications. Donc, s'il ne s'agit pas de tester, de quoi s'agit-il ? Il s'agit de s'imaginer ce que vous essayez de faire avant de partir en court de route pour essayer de le faire. Vous écrivez une spécification qui fixe une petite partie du comportement sous une forme concise, non ambiguë et exécutable. C'est aussi simple que ça. Cela signifie-t'il que vous écrivez des tests ? Non. Cela signifie que vous écrivez des spécifications sur ce que votre code a à faire. Cela signifie que vous spécifiez le comportement de votre code dans le temps. Mais pas trop loin dans le temps. En fait, juste avant d'écrire le code, c'est mieux car c'est quand vous avez autant d'information que vous voulez sous la main à ce moment. Comme avec le développement dirigé par les tests quand il est bien fait, vous travaillez par petits incréments... en spécifiant un petit aspect du comportement à la fois, puis vous l'implémentez. Quand vous réalisez qu'il s'agit de spécifier un comportement et pas écrire des tests, votre point de vue se déplace. Soudain, l'idée d'avoir une classe de test pour chacune de vos classes de production est ridiculement limitant. Et la pensée de tester chacune de vos méthodes avec leurs propres méthodes de test (dans une relation 1 pour 1) sera risible. ?Dave Astels L'accent mis par le Développement dirigé par le comportement [http://en.wikipedia.org/wiki/ Behavior_driven_development] est sur "le langage et les interactions utilisés dans le processus du développement logiciel. Les développeurs dirigés par le comportement utilisent leur langue naturelle en combinaison avec le langage polyvalent de la conception dirigée par le domaine [http:// en.wikipedia.org/wiki/Domain_driven_design] pour décrire le but et le bénéfice de leur code. Ceci permet aux développeurs de se concentrer sur pourquoi le code doit être créé, plutôt que sur les détails techniques, et de minimiser les traductions entre le langage technique dans lequel le code est écrit et le langage du domaine parlé par les "experts du domaine". La classe PHPUnit_Extensions_Story_TestCase ajouter un framework d'histoire (story) qui facilite la définition d'un Langage spécifique au domaine [http://en.wikipedia.org/wiki/Domain- specific_programming_language] pour le développement dirigé par le comportement. Il peut être installé comme ceci : pear install phpunit/PHPUnit_Story A l'intérieur d'un scénario, given(), when() et then() représentent chacun une étape. and() est du même type que les étapes précédentes. Les méthodes suivantes sont déclarées abstract dans PHPUnit_Extensions_Story_TestCase et doivent être implémentées : ? runGiven(&$monde, $action, $parametres) Développement dirigé par le comportement 128 ... ? runWhen(&$monde, $action, $parametres) ... ? runThen(&$monde, $action, $parametres) ... Exemple du jeu de Bowling Dans cette section, nous examinerons l'exemple d'une classe qui calcule le score d'un jeu de bowling. Les règles de ce jeu sont les suivantes : ? Le jeu comprend 10 manches. ? Dans chaque manche, le joueur a deux possibilités de faire tomber 10 quilles. ? Le score pour une manche est le nombre total de quilles tombées, plus des bonus pour les strikes et les spares. ? Un spare, c'est quand le joueur fait tomber les 10 quilles en 2 essais. Le bonus pour ce type de manche est le nombre de quilles renversées lors du lancer suivant. ? Un strike, c'est quand le joueur fait tomber les 10 quilles à son premier essai. Le bonus pour ce type de manche est le nombre de quilles renversées lors des deux lancers suivants. Exemple 13.1, « Spécification pour la classe JeuDeBowling » montre comment les règles précédentes sont écrites comme scénarios de spécification en utilisant PHPUnit_Extensions_Story_TestCase. Exemple 13.1. Spécification pour la classe JeuDeBowling <?php require_once 'PHPUnit/Extensions/Story/TestCase.php'; require_once 'JeuDeBowling.php'; class JeuDeBowlingSpec extends PHPUnit_Extensions_Story_TestCase { /** * scenario */ public function scorePourJeuDansLaGoutiereEst0() { $this->given('Nouvelle partie') ->then('Le score doit être', 0); } /** * scenario */ public function scorePourToutDUnSeulCoupEst20() { $this->given('Nouvelle partie') ->when('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) Développement dirigé par le comportement 129 ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->and('Le joueur lance et renverse', 1) ->then('Le score doit être', 20); } /** * scenario */ public function scorePourUnSpareEt3Est16() { $this->given('Nouvelle partie') ->when('Le joueur lance et renverse', 5) ->and('Le joueur lance et renverse', 5) ->and('Le joueur lance et renverse', 3) ->then('Le score doit être', 16); } /** * scenario */ public function scorePourUnStrikeEt3Est24() { $this->given('Nouvelle partie') ->when('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 3) ->and('Le joueur lance et renverse', 4) ->then('Le score doit être', 24); } /** * scenario */ public function scorePourUnJeuParfaitEst300() { $this->given('Nouvelle partie') ->when('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->and('Le joueur lance et renverse', 10) ->then('Le score doit être', 300); } public function runGiven(&$monde, $action, $parametres) { Développement dirigé par le comportement 130 switch($action) { case 'Nouvelle partie': { $monde['jeu'] = new JeuDeBowling; $monde['lancers'] = 0; } break; default: { return $this->notImplemented($action); } } } public function runWhen(&$monde, $action, $parametres) { switch($action) { case 'Le joueur lance et renverse': { $monde['jeu']->lancerEtRenverser($parametres[0]); $monde['lancers']++; } break; default: { return $this->notImplemented($action); } } } public function runThen(&$monde, $action, $parametres) { switch($action) { case 'Le score doit être': { for ($i = $monde['lancers']; $i < 20; $i++) { $monde['jeu']->lancerEtRenverser(0); } $this->assertEquals($parametres[0], $monde['jeu']->score()); } break; default: { return $this->notImplemented($action); } } } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. JeuDeBowlingSpec [x] Score pour jeu dans la goutiere est 0 Given Nouvelle partie Then Le score doit être 0 [x] Score pour tout d un seul coup est 20 Given Nouvelle partie When Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 Développement dirigé par le comportement 131 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 Then Le score doit être 20 [x] Score pour un spare et 3 est 16 Given Nouvelle partie When Le joueur lance et renverse 5 and Le joueur lance et renverse 5 and Le joueur lance et renverse 3 Then Le score doit être 16 [x] Score pour un strike et 3 est 24 Given Nouvelle partie When Le joueur lance et renverse 10 and Le joueur lance et renverse 3 and Le joueur lance et renverse 4 Then Le score doit être 24 [x] Score pour un jeu parfait est 300 Given Nouvelle partie When Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 Then Le score doit être 300 Scenarios: 5, Failed: 0, Skipped: 0, Incomplete: 0.phpunit --printer PHPUnit_Extensions_S PHPUnit 3.7.0 by Sebastian Bergmann. JeuDeBowlingSpec [x] Score pour jeu dans la goutiere est 0 Given Nouvelle partie Then Le score doit être 0 [x] Score pour tout d un seul coup est 20 Given Nouvelle partie When Le joueur lance et renverse 1 Développement dirigé par le comportement 132 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 and Le joueur lance et renverse 1 Then Le score doit être 20 [x] Score pour un spare et 3 est 16 Given Nouvelle partie When Le joueur lance et renverse 5 and Le joueur lance et renverse 5 and Le joueur lance et renverse 3 Then Le score doit être 16 [x] Score pour un strike et 3 est 24 Given Nouvelle partie When Le joueur lance et renverse 10 and Le joueur lance et renverse 3 and Le joueur lance et renverse 4 Then Le score doit être 24 [x] Score pour un jeu parfait est 300 Given Nouvelle partie When Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 and Le joueur lance et renverse 10 Then Le score doit être 300 Scenarios: 5, Failed: 0, Skipped: 0, Incomplete: 0. 133 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 [http://github.com/sebastianbergmann/php-code-coverage] qui, à son tour, tire partie de la fonctionnalité de couverture d'instructions fournie par l'extension Xdebug [http://www.xdebug.org/] 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 3.7.0 by Sebastian Bergmann. ... Time: 0 seconds OK (3 tests, 3 assertions) Generating report, this may take a moment.phpunit --coverage-html ./rapport CompteBancair PHPUnit 3.7.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. Analyse de couverture de code 134 Figure 14.1. 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 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 { // ... Analyse de couverture de code 135 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 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()); Analyse de couverture de code 136 } /** * 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()); } } ?> 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 14.3. Un test qui indique qu'aucune méthode ne doit être couverte <?php Analyse de couverture de code 137 class IntegrationLivreDOrTest extends PHPUnit_Extensions_Database_TestCase { /** * coversNothing */ public function testAjouteEntree() { $livre_d_or = new LivredOr(); $livre_d_or->addEntry("suzy", "Hello world!"); $queryTable = $this->getConnection()->createQueryTable( 'livre_d_or', 'SELECT * FROM livre_d_or' ); $expectedTable = $this->createFlatXmlDataSet("expectedBook.xml") ->getTable("livre_d_or"); $this->assertTablesEqual($expectedTable, $queryTable); } } ?> 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.4, « Utiliser les annotations codeCoverageIgnore, codeCoverageIgnoreStart et codeCoverageIgnoreEnd ». Exemple 14.4. 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. Analyse de couverture de code 138 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é. Chaque fichier de la liste blanche est ajouté au rapport de couverture de code, qu'il ait été exécuté ou pas. Toutes les lignes d'un tel fichier, incluant celles qui ne sont pas exécutables, sont comptées comme non exécutées. Quand vous configurez processUncoveredFilesFromWhitelist="true" dans votre configuration PHPUnit (voir la section intitulée « Inclure et exclure des fichiers de la couverture de code ») alors ces fichiers seront à inclure par PHP_CodeCoverage pour calculer correctement le nombre de lignes exécutables. Note Merci de noter que le chargement des fichiers de code source réalisé quand processUncoveredFilesFromWhitelist="true" est positionné, peut poser des problèmes quand un fichier de code source contient du code hors de la portée d'une classe ou d'une fonction, par exemple. 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.5. <?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 / 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(); } ?> 139 Chapitre 15. Autres utilisations des tests Une fois que vous avez écrit des tests automatisés, vous découvrirez certainement davantage d'usages pour les tests. En voici quelques exemples. Documentation agile Typiquement, dans un projet développé en utilisant un processus agile, tel que l'Extreme Programming, la documentation ne peut pas suivre les changements fréquents de la conception et du code du projet. l'Extreme Programming réclame la propriété collective du code, donc tous les développeurs ont besoin de savoir comment fonctionne l'intégralité du système. Si vous êtes suffisamment discipliné pour utiliser pour vos tests des "noms parlant" qui décrivent ce qu'une classe doit faire, vous pouvez utiliser la fonctionnalité TestDox de PHPUnit pour générer automatiquement de la documentation pour votre projet en s'appuyant sur ses tests. Cette documentation donne aux développeurs un aperçu de ce que chaque classe du projet est supposée faire. La fonctionnalité TestDox de PHPUnit examine une classe de test et tous les noms de méthode de test pour les convertir les noms au format Camel Case PHP en phrases : testBalanceEstInitialementAZéro() devient "Balance est initialement a zero". S'il existe plusieurs méthodes de test dont les noms ne diffèrent que par un suffixe constitué de un ou plusieurs chiffres, telles que testBalanceNePeutPasEtreNégative() et testBalanceNePeutPasEtreNégative2(), la phrase "Balance ne peut pas etre négative" n'apparaîtra qu'une seule fois, en supposant que tous ces tests ont réussi. Jetons un oeil sur la documentation agile générée pour la classe CompteBancaire (à partir de Exemple 12.1, « Tests pour la classe CompteBancaire »): PHPUnit 3.7.0 by Sebastian Bergmann. CompteBancaire [x] Balance est initialement a zéro [x] Balance ne peut pas devenir négativephpunit --testdox CompteBancaireTest PHPUnit 3.7.0 by Sebastian Bergmann. CompteBancaire [x] Balance est initialement a zéro [x] Balance ne peut pas devenir négative Alternativement, la documentation agile peut être générée en HTML ou au format texte et écrite dans un fichier en utilisant les paramètres --testdox-html et --testdox-text. Note Note du traducteur: les majuscules accentuées ne sont pas correctement gérées, il ne faut donc pas les utiliser. La documentation agile peut être utilisée pour documenter les hypothèses que vous faites sur les paquets externes que vous utilisez dans votre projet. Quand vous utilisez un paquet externe, vous vous exposez au risque que le paquet ne se comportera pas comme vous le prévoyez et que les futures versions du paquet changeront de façon subtile, ce qui cassera votre code sans que vous ne le sachiez. Vous pouvez adresser ces risques en écrivant un test à chaque fois que vous faites une hypothèse. Si votre test réussit, votre hypothèse est valide. Si vous documentez toutes vos hypothèses avec des tests, les futures livraisons du paquet externe ne poseront pas de problème : si les tests réussissent, votre système doit continuer à fonctionner. Autres utilisations des tests 140 Tests transverses à l'équipe Quand vous documentez des hypothèses avec des tests, vous êtes propriétaire des tests. Le fournisseur du paquet - sur lequel vous faîtes des hypothèses - ne connaît rien de vos tests. Si vous voulez avoir une relation plus étroite avec le fournisseur du paquet, vous pouvez utiliser les tests pour communiquer et coordonner vos activités. Quand vous êtes d'accord pour coordonner vos activités avec le fournisseur d'un paquet, vous pouvez écrire les tests ensembles. Faites cela d'une telle façon que les tests révèlent autant d'hypothèses que possible. Les hypothèses cachées sont la mort de la coopération. Avec les tests, vous documentez exactement ce que vous attendez du paquet fourni. Le fournisseur saura que le paquet est prêt quand tous les tests fonctionneront. En utilisant des bouchons (voir le chapitre relatif aux "objets simulacres", précédemment dans ce livre), vous pouvez créer un découplage plus grand entre vous et le fournisseur: le boulot du fournisseur est de faire que les tests fonctionnent avec l'implémentation réelle du paquet. Votre boulot est de faire que les tests fonctionnent sur votre propre code. Jusqu'à ce que vous ayez l'implémentation réelle du paquet fourni, vous utilisez des objets bouchons. Suivant cette approche, deux équipes peuvent développer indépendamment. 141 Chapitre 16. Générateur de squelette Le générateur de squelette de PHPUnit (Skeleton Generator) est un outil qui peut générer des squelettes de classes de test à partir des classes de code de production et vice versa. Il peut être installé en utilisant la commande suivante : pear install phpunit/PHPUnit_SkeletonGenerator Générer un squelettre de classe de cas de test Quand vous écrivez des tests pour du code existant, vous avez à écrire les mêmes fragments de code tels que public function testMethode() { } encore et encore. Le générateur de squelette de PHPUnit peut vous aider en analysant le code d'une classe existante et en générant pour elle un squelette de classe de cas de test. Exemple 16.1. La classe Calculateur <?php class Calculateur { public function additionner($a, $b) { return $a + $b; } } ?> L'exemple suivant montre comment générer un squelette de classe de de test pour une classe appelée Calculateur (see Exemple 16.1, « La classe Calculateur »). PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann. Wrote skeleton for "CalculateurTest" to "/home/sb/CalculateurTest.php".phpunit-skelgen -- PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann. Wrote skeleton for "CalculateurTest" to "/home/sb/CalculateurTest.php". Pour chaque méthode de la classe originelle, il y a aura un cas de test incomplet (voir Chapitre 9, Tests incomplets et sautés) dans la classe de cas de test générée. Classes sous espace de nom et le générateur de squelette Lorsque vous utilisez le générateur de squelette pour générer du code basé sur une classe qui est déclarée dans un espace de nommage (namespace) [http://php.net/namespace] vous devez fournir le nom qualifié de la classe ainsi que le chemin d'accès au fichier source dans lequel elle est déclarée. Par exemple, pour une classe Calculateur qui est déclarée dans l'espace de nommage projet, vous devez invoquer le générateur de squelette comme ceci : Générateur de squelette 142 PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann. Wrote skeleton for "projet\CalculateurTest" to "/home/sb/CalculateurTest.php".phpunit PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann. Wrote skeleton for "projet\CalculateurTest" to "/home/sb/CalculateurTest.php". Ci-dessous se trouve la sortie écran produite par le lancement de la classe de cas de test générée. PHPUnit 3.7.0 by Sebastian Bergmann. I Time: 0 seconds, Memory: 3.50Mb There was 1 incomplete test: 1) CalculateurTest::testAdditionner This test has not been implemented yet. /home/sb/CalculateurTest.php:38 OK, but incomplete or skipped tests! Tests: 1, Assertions: 0, Incomplete: 1.phpunit --bootstrap Calculateur.php --verbose Calc PHPUnit 3.7.0 by Sebastian Bergmann. I Time: 0 seconds, Memory: 3.50Mb There was 1 incomplete test: 1) CalculateurTest::testAdditionner This test has not been implemented yet. /home/sb/CalculateurTest.php:38 OK, but incomplete or skipped tests! Tests: 1, Assertions: 0, Incomplete: 1. Vous pouvez utiliser l'annotation assert dans le bloc de documentation d'une méthode pour générer automatiquement des tests simples mais significatifs au lieu de cas de tests incomplets. Exemple 16.2, « La classe Calculateur avec des annotations assert » montre un exemple. Exemple 16.2. La classe Calculateur avec des annotations assert <?php class Calculateur { /** * assert (0, 0) == 0 * assert (0, 1) == 1 * assert (1, 0) == 1 * assert (1, 1) == 2 */ public function additionner($a, $b) { return $a + $b; } } ?> Chaque méthode de la classe originelle est contrôlée à la recherche d'annotations assert. Celles- ci sont transformées en code de test comme Générateur de squelette 143 /** * Generated from assert (0, 0) == 0. */ public function testAdditionner() { $o = new Calculateur; $this->assertEquals(0, $o->additionner(0, 0)); } Ci-dessous se trouve la sortie écran produite par le lancement de la classe de cas de test générée. PHPUnit 3.7.0 by Sebastian Bergmann. .... Time: 0 seconds, Memory: 3.50Mb OK (4 tests, 4 assertions)phpunit --bootstrap Calculateur.php --verbose CalculateurTest PHPUnit 3.7.0 by Sebastian Bergmann. .... Time: 0 seconds, Memory: 3.50Mb OK (4 tests, 4 assertions) Tableau 16.1, « Variantes gérées par l'annotation assert » montre les variantes gérées par l'annotation assert et de quelle façon elles sont transformées en code de test. Tableau 16.1. Variantes gérées par l'annotation assert Annotation Transformée en assert (...) == X assertEquals(X, methode(...)) assert (...) != X assertNotEquals(X, methode(...)) assert (...) === X assertSame(X, methode(...)) assert (...) !== X assertNotSame(X, methode(...)) assert (...) > X assertGreaterThan(X, methode(...)) assert (...) >= X assertGreaterThanOrEqual(X, methode(...)) assert (...) < X assertLessThan(X, methode(...)) assert (...) <= X assertLessThanOrEqual(X, methode(...)) assert (...) throws X expectedException X Générer un squelette de classe à partir d'une classe de cas de test Lorsque vous pratiquez le développement dirigé par les tests (voir Chapitre 12, Développement dirigé par les tests) et que vous écrivez vos tests avant le code que les tests vérifient, PHPUnit peut vous aider à générer des squelettes de classe à partir des classes de cas de test. Suivant la convention selon laquelle les tests pour une classe Unit sont écrit dans une classe nommée UnitTest, le source de la classe de cas de test est inspecté à la recherche de variables qui référencent des objets de la classe Unit puis est analysé pour savoir quelles méthodes sont appelées sur ces objets. Générateur de squelette 144 Par exemple, jetez un oeil à Exemple 16.4, « Le squelette généré de la classe JeuDeBowling » qui a été généré en se basant sur l'analyse de Exemple 16.3, « La classe JeuDeBowlingTest ». Exemple 16.3. La classe JeuDeBowlingTest <?php class JeuDeBowlingTest extends PHPUnit_Framework_TestCase { protected $jeu; protected function setUp() { $this->jeu = new JeuDeBowling; } protected function lancePlusieursEtRenverse($n, $quilles) { for ($i = 0; $i < $n; $i++) { $this->jeu->renverse($quilles); } } public function testScorePourJeuDansLaRigoleEst0() { $this->lancePlusieursEtRenverse(20, 0); $this->assertEquals(0, $this->jeu->score()); } } ?> PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann. Wrote skeleton for "JeuDeBowling" to "./JeuDeBowling.php".phpunit-skelgen --class JeuDeBo PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann. Wrote skeleton for "JeuDeBowling" to "./JeuDeBowling.php". Exemple 16.4. Le squelette généré de la classe JeuDeBowling <?php /** * Generated by PHPUnit_SkeletonGenerator on 2012-01-09 at 16:55:58. */ class JeuDeBowling { /** * todo Implement renverse(). */ public function renverse() { // Remove the following line when you implement this method. throw new RuntimeException('Not yet implemented.'); } /** * todo Implement score(). */ public function score() { // Remove the following line when you implement this method. throw new RuntimeException('Not yet implemented.'); Générateur de squelette 145 } } ?> Ci-dessous se trouve la sortie écran produite par le lancement de la classe de cas de test générée. PHPUnit 3.7.0 by Sebastian Bergmann. E Time: 0 seconds, Memory: 3.50Mb There was 1 error: 1) JeuDeBowlingTest::testScorePourJeuDansLaRigoleEst0 RuntimeException: Not yet implemented. /home/sb/JeuDeBowling.php:13 /home/sb/JeuDeBowlingTest.php:14 /home/sb/JeuDeBowlingTest.php:20 FAILURES! Tests: 1, Assertions: 0, Errors: 1.phpunit --bootstrap JeuDeBowling.php JeuDeBowlingTest PHPUnit 3.7.0 by Sebastian Bergmann. E Time: 0 seconds, Memory: 3.50Mb There was 1 error: 1) JeuDeBowlingTest::testScorePourJeuDansLaRigoleEst0 RuntimeException: Not yet implemented. /home/sb/JeuDeBowling.php:13 /home/sb/JeuDeBowlingTest.php:14 /home/sb/JeuDeBowlingTest.php:20 FAILURES! Tests: 1, Assertions: 0, Errors: 1. 146 Chapitre 17. PHPUnit et Selenium Selenium Server Selenium Server [http://seleniumhq.org/] est un outil de test qui vous permet d'écrire des tests automatisés de l'interface utilisateur d'applications web dans n'importe quel langage et menés sur n'importe quel site web HTTP en utilisant n'importe quel navigateur courant. Il réalise des tâches automatisée dans le navigateur en pilotant le processus du navigateur via le système d'exploitation. Les tests Selenium s'exécutent directement dans un navigateur, exactement comme des utilisateurs réels le feraient. Ces tests peuvent être utilisés à la fois comme tests de validation (en exécutant des tests au plus haut niveau sur le système intégré au lieu de simplement tester chaque unité du système indépendamment) et des tests de compatibilité pour les navigateurs (en testant l'application web sur différents systèmes d'exploitation et différents navigateurs). Le seul scénario géré par PHPUnit_Selenium est celui du serveur Selenium 2.x. Le serveur peut être accédé via l'API classique Selenium RC déjà présente dans la version 1.x ou avec l'API serveur WebDriver (partiellement implémentée) à partir de PHPUnit_Selenium 1.2. La raison derrière cette décision est que Selenium 2 est rétro compatible et que Selenium RC n'est désormais plus maintenu. Installation Premièrement, installer Selenium Server: 1. Télécharger une archive du Serveur Selenium [http://seleniumhq.org/download/]. 2. Dézipper l'archive et copier selenium-server-standalone-2.20.0.jar (contrôler le suffixe de version) dans /usr/local/bin, par exemple. 3. Lancer le serveur Selenium en exécutant java -jar /usr/local/bin/selenium- server-standalone-2.20.0.jar. Deuxièmement, installer le paquet PHPUnit_Selenium, nécessaire pour accéder nativement au serveur Selenium depuis PHPUnit: pear install phpunit/PHPUnit_Selenium Maintenant, nous pouvons envoyer des commandes au serveur Selenium en utilisant son protocole client/serveur. PHPUnit_Extensions_Selenium2TestCase Le cas de test PHPUnit_Extensions_Selenium2TestCase vous permet d'utiliser l'API WebDriver (partiellement implémentée). Exemple 17.1, « Exemple d'utilisation de PHPUnit_Extensions_Selenium2TestCase » montre comment tester le contenu de l'élément <title> du site web http://www.example.com/. Exemple 17.1. Exemple d'utilisation de PHPUnit_Extensions_Selenium2TestCase <?php PHPUnit et Selenium 147 class WebTest extends PHPUnit_Extensions_Selenium2TestCase { protected function setUp() { $this->setBrowser('firefox'); $this->setBrowserUrl('http://www.example.com/'); } public function testTitle() { $this->url('http://www.example.com/'); $this->assertEquals('Example WWW Page', $this->title()); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 28 seconds, Memory: 3.00Mb There was 1 failure: 1) WebTest::testTitle Failed asserting that two strings are equal. --- Expected +++ Actual -'Example WWW Page' +'IANA ? Example domains' /home/giorgio/WebTest.php:13 FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit WebTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 28 seconds, Memory: 3.00Mb There was 1 failure: 1) WebTest::testTitle Failed asserting that two strings are equal. --- Expected +++ Actual -'Example WWW Page' +'IANA ? Example domains' /home/giorgio/WebTest.php:13 FAILURES! Tests: 1, Assertions: 1, Failures: 1. Les commandes de Selenium2TestCare sont implémentées via __call(). Merci de vous référer à the end-to-end test for PHPUnit_Extensions_Selenium2TestCase [https://github.com/ sebastianbergmann/phpunit-selenium/blob/master/Tests/Selenium2TestCaseTest.php] pour la liste de toutes les fonctionnalités prises en charge. PHPUnit et Selenium 148 PHPUnit_Extensions_SeleniumTestCase L'extension de cas de test PHPUnit_Extensions_SeleniumTestCase implémente le protocole client/serveur pour parler au serveur Selenium ainsi que des méthodes de vérification spécialisées pour le test web. Exemple 17.2, « Exemple d'utilisation de PHPUnit_Extensions_SeleniumTestCase » montre comment tester le contenu de l'élément <title> du site web http://www.example.com/. Exemple 17.2. Exemple d'utilisation de PHPUnit_Extensions_SeleniumTestCase <?php require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; class WebTest extends PHPUnit_Extensions_SeleniumTestCase { protected function setUp() { $this->setBrowser('*firefox'); $this->setBrowserUrl('http://www.example.com/'); } public function testTitle() { $this->open('http://www.example.com/'); $this->assertTitle('Example WWW Page'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 9 seconds, Memory: 6.00Mb There was 1 failure: 1) WebTest::testTitle Current URL: http://www.iana.org/domains/example/ Failed asserting that 'IANA ? Example domains' matches PCRE pattern "/Example WWW Page/". FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit WebTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 9 seconds, Memory: 6.00Mb There was 1 failure: 1) WebTest::testTitle Current URL: http://www.iana.org/domains/example/ Failed asserting that 'IANA ? Example domains' matches PCRE pattern "/Example WWW Page/". FAILURES! PHPUnit et Selenium 149 Tests: 1, Assertions: 1, Failures: 1. Contrairement à la classe PHPUnit_Framework_TestCase, les classes de cas de test qui héritent de PHPUnit_Extensions_SeleniumTestCase doivent fournir une méthode setUp(). Cette méthode est utilisée pour configurer la session du serveur Selenium. Voir Tableau 17.1, « API de Selenium Server: configuration » pour la liste des méthodes qui sont disponibles pour cela. Tableau 17.1. API de Selenium Server: configuration Méthode Signification void setBrowser(string $navigateur) Règle le navigateur que le serveur Selenium Server doit utiliser. void setBrowserUrl(string $urlNavigateur) Règle l'URL de base pour les tests. void setHost(string $hote) Règle le nom d'hôte pour la connexion au serveur Selenium Server. void setPort(int $port) Règle le port pour la connexion au serveur Selenium Server. void setTimeout(int $delaiExpiration) Règle le délai d'expiration pour la connexion au serveur Selenium Server server. void setSleep(int $secondes) Règle le nombre de secondes durant lesquelles le client Selenium Server client doit attendre entre l'envoi de commandes au serveur Selenium Server. PHPUnit peut facultativement faire une capture d'écran quand un test Selenium échoue. Pour activer ceci, réglez $captureScreenshotOnFailure, $screenshotPath et $screenshotUrl dans votre classe de cas de test comme montré dans Exemple 17.3, « Faire une capture d'écran quand un test échoue ». Exemple 17.3. Faire une capture d'écran quand un test échoue <?php require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; class WebTest extends PHPUnit_Extensions_SeleniumTestCase { protected $captureScreenshotOnFailure = TRUE; protected $screenshotPath = '/var/www/localhost/htdocs/screenshots'; protected $screenshotUrl = 'http://localhost/screenshots'; protected function setUp() { $this->setBrowser('*firefox'); $this->setBrowserUrl('http://www.example.com/'); } public function testTitle() { $this->open('http://www.example.com/'); $this->assertTitle('Example WWW Page'); } } ?> PHPUnit 3.7.0 by Sebastian Bergmann. PHPUnit et Selenium 150 F Time: 7 seconds, Memory: 6.00Mb There was 1 failure: 1) WebTest::testTitle Current URL: http://www.iana.org/domains/example/ Screenshot: http://localhost/screenshots/334b080f2364b5f11568ee1c7f6742c9.png Failed asserting that 'IANA ? Example domains' matches PCRE pattern "/Example WWW Page/". FAILURES! Tests: 1, Assertions: 1, Failures: 1.phpunit WebTest PHPUnit 3.7.0 by Sebastian Bergmann. F Time: 7 seconds, Memory: 6.00Mb There was 1 failure: 1) WebTest::testTitle Current URL: http://www.iana.org/domains/example/ Screenshot: http://localhost/screenshots/334b080f2364b5f11568ee1c7f6742c9.png Failed asserting that 'IANA ? Example domains' matches PCRE pattern "/Example WWW Page/". FAILURES! Tests: 1, Assertions: 1, Failures: 1. Vous pouvez exécuter chaque test en utilisant une série de navigateurs : au lieu d'utiliser setBrowser() pour indiquer un seul navigateur, vous déclarez un tableau public static nommé $browsers dans votre classe de cas de test. Chaque élément de ce tableau décrit la configuration d'un navigateur. Chacun de ces navigateurs peut être hébergé par différents serveurs Selenium Server. Exemple 17.4, « Régler la configuration de multiples navigateurs » montre un exemple. Exemple 17.4. Régler la configuration de multiples navigateurs <?php require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; class WebTest extends PHPUnit_Extensions_SeleniumTestCase { public static $browsers = array( array( 'name' => 'Firefox sur Linux', 'browser' => '*firefox', 'host' => 'ma.box.linux', 'port' => 4444, 'timeout' => 30000, ), array( 'name' => 'Safari sur MacOS X', 'browser' => '*safari', 'host' => 'ma.box.macosx', 'port' => 4444, 'timeout' => 30000, ), array( 'name' => 'Safari sur Windows XP', PHPUnit et Selenium 151 'browser' => '*custom C:\Program Files\Safari\Safari.exe -url', 'host' => 'ma.box.windowsxp', 'port' => 4444, 'timeout' => 30000, ), array( 'name' => 'Internet Explorer sur Windows XP', 'browser' => '*iexplore', 'host' => 'ma.box.windowsxp', 'port' => 4444, 'timeout' => 30000, ) ); protected function setUp() { $this->setBrowserUrl('http://www.example.com/'); } public function testTitle() { $this->open('http://www.example.com/'); $this->assertTitle('Example Web Page'); } } ?> PHPUnit_Extensions_SeleniumTestCase peut rassembler des informations de couverture de code pour les tests lancés via Selenium: 1. Copier PHPUnit/Extensions/SeleniumTestCase/phpunit_coverage.php dans le répertoire racine de votre serveur web. 2. Dans le fichier de configuration du serveur web php.ini, configurez PHPUnit/ Extensions/SeleniumTestCase/prepend.php et PHPUnit/Extensions/ SeleniumTestCase/append.php respectivement comme auto_prepend_file et auto_append_file. 3. Dans votre classe de cas de test qui hérite de PHPUnit_Extensions_SeleniumTestCase, utilisez protected $coverageScriptUrl = 'http://host/phpunit_coverage.php'; pour configurer l'URL pour le script phpunit_coverage.php. Tableau 17.2, « Assertions » liste les diverses méthodes de vérification que PHPUnit_Extensions_SeleniumTestCase fournit. Tableau 17.2. Assertions Assertion Signification void assertElementValueEquals(string $localisateur, string $texte) Rapporte une erreur si la valeur de l'élément identifié par $localisateur n'est pas égale au $texte donné. void assertElementValueNotEquals(string $localisateur, string $texte) Rapporte une erreur si la valeur de l'élément identifié par $localisateur est égale au $texte donné. void assertElementValueContains(string $localisateur, string $texte) Rapporte une erreur si la valeur de l'élément identifié par $localisateur ne contient pas le $texte donné. PHPUnit et Selenium 152 Assertion Signification void assertElementValueNotContains(string $localisateur, string $texte) Rapporte une erreur si la valeur de l'élément identifié par $localisateur contient le $texte donné. void assertElementContainsText(string $localisateur, string $texte) Rapporte une erreur si l'élément identifié par $localisateur ne contient pas le $texte donné. void assertElementNotContainsText(string $localisateur, string $texte) Rapporte une erreur si l'élément identifié par $localisateur contient le $texte donné. void assertSelectHasOption(string $localisateurDeSelect, string $option) Rapporte une erreur si l'option de liste déroulante donnée n'est pas disponible. void assertSelectNotHasOption(string $localisateurDeSelect, string $option) Rapporte une erreur si l'option de liste déroulante donnée est disponible. void assertSelected($localisateurDeSelect, $option) Rapporte une erreur si l'étiquette de liste déroulante donnée n'est pas sélectionnée. void assertNotSelected($localisateurDeSelect, $option) Rapporte une erreur si l'étiquette de liste déroulante donnée est sélectionnée. void assertIsSelected(string $localisateurDeSelect, string $valeur) Rapporte une erreur si la valeur donnée n'est pas sélectionnée dans la liste déroulante. void assertIsNotSelected(string $localisateurDeSelect, string $valeur) Rapporte une erreur si la valeur donnée est sélectionnée dans la liste déroulante. Tableau 17.3, « Méthodes canevas » montre la méthode canevas de PHPUnit_Extensions_SeleniumTestCase: Tableau 17.3. Méthodes canevas Méthode Signification void defaultAssertions() Surcharge pour exécuter des assertions qui sont partagées par tous les tests d'un cas de test. Cette méthode est appelée après chaque commande qui est envoyée au serveur Selenium Server. Merci de vous référer à la documentation des commandes Selenium [http://release.seleniumhq.org/ selenium-core/1.0.1/reference.html] pour une référence des commandes disponibles et comment elles sont utilisées. Les commandes de Selenium 1 sont implémentées dynamiquement via __call. Référez-vous également aux documents de l'API pour PHPUnit_Extensions_SeleniumTestCase_Driver::__call() [https://github.com/sebastianbergmann/ phpunit-selenium/blob/master/PHPUnit/Extensions/SeleniumTestCase/Driver.php#L410] pour une liste de toutes les méthodes gérées du côté PHP, avec les paramètres et le type de retourné quand ils sont disponibles. En utilisant la méthode runSelenese($filename), vous pouvez également lancer un test Selenium à partir de ses spécifications Selenese/HTML. Plus encore, en utilisant l'attribut statique PHPUnit et Selenium 153 $seleneseDirectory, vous pouvez créer automatiquement des objets tests à partir d'un répertoire qui contient des fichiers Selenese/HTML. Le répertoire indiqué est parcouru récursivement à la recherche de fichiers .htm qui sont supposés contenir du Selenese/HTML. Exemple 17.5, « Utiliser un répertoire de fichiers Selenese/HTML comme tests » montre un exemple. Exemple 17.5. Utiliser un répertoire de fichiers Selenese/HTML comme tests <?php require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; class SeleneseTests extends PHPUnit_Extensions_SeleniumTestCase { public static $seleneseDirectory = '/chemin/vers/fichiers'; } ?> A partir de Selenium 1.1.1, une fonctionnalité expérimentale est incluse permettant à un utilisateur de partager la session entre plusieurs tests. Le seul cas géré est le partage de session entre tous les tests quand un unique navigateur est utilisé. Appelez PHPUnit_Extensions_SeleniumTestCase::shareSession(true) dans votre fichier amorce pour activer le partage de session. La session sera réinitialisée dans le cas où un test échoue (en échec ou incomplet); c'est à la charge de l'utilisateur d'éviter les interactions entre des tests en réinitialisant des cookies ou en se déconnectant de l'application testée (avec une méthode tearDown()). 154 Chapitre 18. Journalisation PHPUnit peut produire plusieurs types de fichiers de journalisations (logs). Résultats de test (XML) Le fichier de journalisation XML pour les tests produits par PHPUnit est basé sur celui qui est utilisé par la tâche JUnit de l'outil Apache Ant [http://ant.apache.org/manual/OptionalTasks/ junit.html]. L'exemple suivant montre que le fichier de journalisation XML généré pour les tests dans TestTableau: <?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="TestTableau" file="/home/sb/TestTableau.php" tests="2" assertions="2" failures="0" errors="0" time="0.016030"> <testcase name="testLeNouveauTableauEstVide" class="TestTableau" file="/home/sb/TestTableau.php" line="6" assertions="1" time="0.008044"/> <testcase name="testLeTableauContientUnElement" class="TestTableau" file="/home/sb/TestTableau.php" line="15" assertions="1" time="0.007986"/> </testsuite> </testsuites> Le fichier de journalisation XML suivant a été généré pour deux tests, testEchec et testErreur, à partir d'une classe de cas de test nommée EchecErreurTest et montre comment les échecs et les erreurs sont signalés. <?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="EchecErreurTest" file="/home/sb/EchecErreurTest.php" tests="2" assertions="1" failures="1" errors="1" time="0.019744"> <testcase name="testFailure" class="EchecErreurTest" file="/home/sb/EchecErreurTest.php" line="6" assertions="1" time="0.011456"> <failure type="PHPUnit_Framework_ExpectationFailedException"> testFailure(EchecErreurTest) Failed asserting that integer:2 matches expected value integer:1. /home/sb/EchecErreurTest.php:8 </failure> Journalisation 155 </testcase> <testcase name="testError" class="EchecErreurTest" file="/home/sb/EchecErreurTest.php" line="11" assertions="0" time="0.008288"> <error type="Exception">testError(EchecErreurTest) Exception: /home/sb/EchecErreurTest.php:13 </error> </testcase> </testsuite> </testsuites> Résultats de test (TAP) Le protocole Test Anything Protocol (TAP) [http://testanything.org/] est une interface Perl simple au format texte entre les modules de test. L'exemple suivant montre le fichier de journalisation TAP généré pour les tests de TableauTest: TAP version 13 ok 1 - testNouveauTableauEstVide(TableauTest) ok 2 - testTableauContientUnElement(TableauTest) 1..2 Le fichier de journalisation TAP a été généré pour deux tests, testEchec et testErreur à partir d'une classe de cas de test nommée EchecErreurTest et montre comme les échecs et les erreurs sont signalés. TAP version 13 not ok 1 - Failure: testEchec(EchecErreurTest) --- message: 'Failed asserting that <integer:2> matches expected value <integer:1>.' severity: fail data: got: 2 expected: 1 ... not ok 2 - Error: testErreur(EchecErreurTest) 1..2 Résultats de test (JSON) La notation objet JavaScript (JavaScript Object Notation ou JSON [http://www.json.org/]) est un format léger d'échange de données. L'exemple suivant montre les messages JSON générés pour les tests dans TableauTest: {"event":"suiteStart","suite":"TableauTest","tests":2} {"event":"test","suite":"TableauTest", "test":"testNouveauTableauEstVide(TableauTest)","status":"pass", "time":0.000460147858,"trace":[],"message":""} {"event":"test","suite":"TableauTest", "test":"testTableauContientUnElement(TableauTest)","status":"pass", "time":0.000422954559,"trace":[],"message":""} Les messages JSON suivants ont été générés pour deux tests, testEchec et testErreur, d'une classe de cas de test nommée EchecErreurTest et monte comment les échecs et les erreurs sont signalées. Journalisation 156 {"event":"suiteStart","suite":"EchecErreurTest","tests":2} {"event":"test","suite":"EchecErreurTest", "test":"testEchec(EchecErreurTest)","status":"fail", "time":0.0082459449768066,"trace":[], "message":"Failed asserting that <integer:2> is equal to <integer:1>."} {"event":"test","suite":"EchecErreurTest", "test":"testErreur(EchecErreurTest)","status":"error", "time":0.0083680152893066,"trace":[],"message":""} Couverture de code (XML) La journalisation au format XML des informations de couverture de code produite par PHPUnit est faiblement basé sur celui utilisé par Clover [http://www.atlassian.com/software/clover/]. L'exemple suivant montre le fichier de journalisation XML généré pour les tests dans CompteBancaireTest: <?xml version="1.0" encoding="UTF-8"?> <coverage generated="1184835473" phpunit="3.6.0"> <project name="CompteBancaireTest" timestamp="1184835473"> <file name="/home/sb/CompteBancaire.php"> <class name="CompteBancaireException"> <metrics methods="0" coveredmethods="0" statements="0" coveredstatements="0" elements="0" coveredelements="0"/> </class> <class name="CompteBancaire"> <metrics methods="4" coveredmethods="4" statements="13" coveredstatements="5" elements="17" coveredelements="9"/> </class> <line num="77" type="method" count="3"/> <line num="79" type="stmt" count="3"/> <line num="89" type="method" count="2"/> <line num="91" type="stmt" count="2"/> <line num="92" type="stmt" count="0"/> <line num="93" type="stmt" count="0"/> <line num="94" type="stmt" count="2"/> <line num="96" type="stmt" count="0"/> <line num="105" type="method" count="1"/> <line num="107" type="stmt" count="1"/> <line num="109" type="stmt" count="0"/> <line num="119" type="method" count="1"/> <line num="121" type="stmt" count="1"/> <line num="123" type="stmt" count="0"/> <metrics loc="126" ncloc="37" classes="2" methods="4" coveredmethods="4" statements="13" coveredstatements="5" elements="17" coveredelements="9"/> </file> <metrics files="1" loc="126" ncloc="37" classes="2" methods="4" coveredmethods="4" statements="13" coveredstatements="5" elements="17" coveredelements="9"/> </project> </coverage> Couverture de code (TEXTE) Sortie de couverture de code humainement lisible pour la ligne de commandes ou un fichier texte. Le but de ce format de sortie est de fournir un aperçu rapide de couverture en travaillant sur un petit ensemble de classes. Pour des projets plus grand cette sortie peut être utile pour obtenir un aperçu rapide de la couverture des projets ou quand il est utilisé avec la fonctionnalité --filter. Quand c'est utilisé à partir de la ligne de commande en écrivant sur php://stdout, cela prend en compte le réglage --colors. Ecrire sur la sortie standard est l'option par défaut quand on utilise la ligne de commandes. Par défaut, ceci ne montrera que les fichiers qui ont au moins une ligne couverte. Ceci Journalisation 157 peut être modifié via l'option de configuration xml showUncoveredFiles Voir la section intitulée « Journalisation ». Figure 18.1. Sortie de couverture de code en couleurs sur la ligne de commandes 158 Chapitre 19. Etendre PHPUnit PHPUnit peut être étendu de multiples façon pour rendre l'écriture des tests plus facile et personnaliser le retour que vous obtenez des tests exécutés. Voici les points de départs communs pour étendre PHPUnit. Sous-classe PHPUnit_Framework_TestCase Ecrivez des assertions personnalisées et des méthodes utilitaires dans une sous classe abstraite de PHPUnit_Framework_TestCase et faites hériter vos classes de cas de test de cette classe. C'est une des façon les plus faciles pour étendre PHPUnit. Ecrire des assertions personnalisées Lorsqu'on écrit des assertions personnalisées, une bonne pratique consiste à suivre la façon dont PHPUnit implémente ses propres assertions. Comme vous pouvez le voir dans Exemple 19.1, « Les méthodes assertTrue() et isTrue() de la classe PHPUnit_Framework_Assert », la méthode assertTrue() n'est qu'un enrobeur des méthodes isTrue() et assertThat(): isTrue() crée un objet de correspondance qui est passé à assertThat() pour évaluation. Exemple 19.1. Les méthodes assertTrue() et isTrue() de la classe PHPUnit_Framework_Assert <?php abstract class PHPUnit_Framework_Assert { // ... /** * Asserts that a condition is true. * * param boolean $condition * param string $message * throws PHPUnit_Framework_AssertionFailedError */ public static function assertTrue($condition, $message = '') { self::assertThat($condition, self::isTrue(), $message); } // ... /** * Returns a PHPUnit_Framework_Constraint_IsTrue matcher object. * * return PHPUnit_Framework_Constraint_IsTrue * since Method available since Release 3.3.0 */ public static function isTrue() { return new PHPUnit_Framework_Constraint_IsTrue; } // ... }?> Exemple 19.2, « La classe PHPUnit_Framework_Constraint_IsTrue » montre comment PHPUnit_Framework_Constraint_IsTrue étend la classe abstraite de base pour des objets de correspondance (ou des contraintes), PHPUnit_Framework_Constraint. Etendre PHPUnit 159 Exemple 19.2. La classe PHPUnit_Framework_Constraint_IsTrue <?php class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns TRUE if the * constraint is met, FALSE otherwise. * * param mixed $other Value or object to evaluate. * return bool */ public function evaluate($other) { return $other === TRUE; } /** * Returns a string representation of the constraint. * * return string */ public function toString() { return 'is true'; } }?> L'effort d'implémentation des méthodes assertTrue() et isTrue() ainsi que la classe PHPUnit_Framework_Constraint_IsTrue tire bénéfice du fait que assertThat() prend automatiquement soin d'évaluer l'assertion et les tâches de suivi comme le décompte à des fins de statistique. Plus encore, la méthode isTrue() peut être utilisée comme un matcher lors de la configuration d'objets simulacres. Implémenter PHPUnit_Framework_TestListener Exemple 19.3, « Un simple moniteur de test » montre une implémentation simple de l'interface PHPUnit_Framework_TestListener. Exemple 19.3. Un simple moniteur de test <?php class SimpleTestListener implements PHPUnit_Framework_TestListener { public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("Error while running test '%s'. ", $test->getName()); } public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionF { printf("Test '%s' failed. ", $test->getName()); } public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("Test '%s' is incomplete. ", $test->getName()); } Etendre PHPUnit 160 public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("Test '%s' has been skipped. ", $test->getName()); } public function startTest(PHPUnit_Framework_Test $test) { printf("Test '%s' started. ", $test->getName()); } public function endTest(PHPUnit_Framework_Test $test, $time) { printf("Test '%s' ended. ", $test->getName()); } public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { printf("TestSuite '%s' started. ", $suite->getName()); } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { printf("TestSuite '%s' ended. ", $suite->getName()); } } ?> Dans la section intitulée « Moniteurs de tests » vous pouvez voir comment configurer PHPUnit pour brancher votre moniteur de test lors de l'exécution des tests. Sous classer PHPUnit_Extensions_TestDecorator Vous pouvez encapsuler des cas de test ou des séries de tests dans une sous-classe de PHPUnit_Extensions_TestDecorator et utiliser le Design Pattern Decorator pour réaliser certaines actions avant et après que les tests sont exécutés. PHPUnit apporte deux décorateurs de test concrets: PHPUnit_Extensions_RepeatedTest et PHPUnit_Extensions_TestSetup. Le premier est utilisé pour exécuter de manière répétée un test et ne le comptabiliser comme succès que si toutes les itérations ont réussi. Le second est discuté dans Chapitre 6, Fixtures. Exemple 19.4, « Le décorateur RepeatedTest » montre une version raccourcie du décorateur de test PHPUnit_Extensions_RepeatedTest qui illustre comment écrire vos propres décorateurs de tests. Exemple 19.4. Le décorateur RepeatedTest <?php require_once 'PHPUnit/Extensions/TestDecorator.php'; class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator { private $timesRepeat = 1; public function __construct(PHPUnit_Framework_Test $test, $timesRepeat = 1) { parent::__construct($test); if (is_integer($timesRepeat) && Etendre PHPUnit 161 $timesRepeat >= 0) { $this->timesRepeat = $timesRepeat; } } public function count() { return $this->timesRepeat * $this->test->count(); } public function run(PHPUnit_Framework_TestResult $result = NULL) { if ($result === NULL) { $result = $this->createResult(); } for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) { $this->test->run($result); } return $result; } } ?> Implémenter PHPUnit_Framework_Test L'interface PHPUnit_Framework_Test est restreinte et facile à implémenter. Vous pouvez écrire une implémentation de PHPUnit_Framework_Test qui est plus simple que PHPUnit_Framework_TestCase et qui exécute des tests dirigés par les données, par exemple. Exemple 19.5, « Un test dirigé par les données » montre une classe de cas de test dirigé par les tests qui compare les valeurs d'un fichier contenant des valeurs séparées par des virgules (CSV). Chaque ligne d'un tel fichier ressemble à foo;bar, où la première valeur est celle que nous attendons et la seconde valeur celle constatée. Exemple 19.5. Un test dirigé par les données <?php class DirigeParLesDonneesTest implements PHPUnit_Framework_Test { private $lines; public function __construct($dataFile) { $this->lines = file($dataFile); } public function count() { return 1; } public function run(PHPUnit_Framework_TestResult $result = NULL) { if ($result === NULL) { $result = new PHPUnit_Framework_TestResult; } foreach ($this->lines as $line) { $result->startTest($this); PHP_Timer::start(); Etendre PHPUnit 162 $stopTime = NULL; list($expected, $actual) = explode(';', $line); try { PHPUnit_Framework_Assert::assertEquals( trim($expected), trim($actual) ); } catch (PHPUnit_Framework_AssertionFailedError $e) { $stopTime = PHP_Timer::stop(); $result->addFailure($this, $e, $stopTime); } catch (Exception $e) { $stopTime = PHP_Timer::stop(); $result->addError($this, $e, $stopTime); } if ($stopTime === NULL) { $stopTime = PHP_Timer::stop(); } $result->endTest($this, $stopTime); } return $result; } } $test = new DataDrivenTest('fichier_donnees.csv'); $resultat = PHPUnit_TextUI_TestRunner::run($test); ?> PHPUnit 3.7.0 by Sebastian Bergmann. .F Time: 0 seconds There was 1 failure: 1) DirigeParLesDonneesTest Failed asserting that two strings are equal. expected string <bar> difference < x> got string <baz> /home/sb/DirigeParLesDonneesTest.php:32 /home/sb/DirigeParLesDonneesTest.php:53 FAILURES! Tests: 2, Failures: 1. 163 Annexe A. Assertions Tableau A.1, « Assertions » montre toutes les variétés d'assertions. Tableau A.1. Assertions Assertion assertArrayHasKey($key, $array, $message = '') assertArrayNotHasKey($key, $array, $message = '') assertAttributeContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = FALSE, $checkForObjectIdentity = TRUE) assertAttributeContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = NULL, $message = '') assertAttributeCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '') assertAttributeEmpty($haystackAttributeName, $haystackClassOrObject, $message = '') assertAttributeEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) assertAttributeGreaterThan($expected, $actualAttributeName, $actualClassOrObject, $message = '') assertAttributeGreaterThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '') assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '') assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '') assertAttributeLessThan($expected, $actualAttributeName, $actualClassOrObject, $message = '') assertAttributeLessThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '') assertAttributeNotContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = FALSE, $checkForObjectIdentity = TRUE) assertAttributeNotContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = NULL, $message = '') assertAttributeNotCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '') assertAttributeNotEmpty($haystackAttributeName, $haystackClassOrObject, $message = '') assertAttributeNotEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '') assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '') Assertions 164 Assertion assertAttributeNotSame($expected, $actualAttributeName, $actualClassOrObject, $message = '') assertAttributeSame($expected, $actualAttributeName, $actualClassOrObject, $message = '') assertClassHasAttribute($attributeName, $className, $message = '') assertClassHasStaticAttribute($attributeName, $className, $message = '') assertClassNotHasAttribute($attributeName, $className, $message = '') assertClassNotHasStaticAttribute($attributeName, $className, $message = '') assertContains($needle, $haystack, $message = '', $ignoreCase = FALSE, $checkForObjectIdentity = TRUE) assertContainsOnly($type, $haystack, $isNativeType = NULL, $message = '') assertContainsOnlyInstancesOf($classname, $haystack, $message = '') assertCount($expectedCount, $haystack, $message = '') assertEmpty($actual, $message = '') assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, $checkAttributes = FALSE, $message = '') assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) assertFalse($condition, $message = '') assertFileEquals($expected, $actual, $message = '', $canonicalize = FALSE, $ignoreCase = FALSE) assertFileExists($filename, $message = '') assertFileNotEquals($expected, $actual, $message = '', $canonicalize = FALSE, $ignoreCase = FALSE) assertFileNotExists($filename, $message = '') assertGreaterThan($expected, $actual, $message = '') assertGreaterThanOrEqual($expected, $actual, $message = '') assertInstanceOf($expected, $actual, $message = '') assertInternalType($expected, $actual, $message = '') assertJsonFileEqualsJsonFile($expectedFile, $actualFile, $message = '') assertJsonFileNotEqualsJsonFile($expectedFile, $actualFile, $message = '') assertJsonStringEqualsJsonFile($expectedFile, $actualJson, $message = '') assertJsonStringEqualsJsonString($expectedJson, $actualJson, $message = '') assertJsonStringNotEqualsJsonFile($expectedFile, $actualJson, $message = '') assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, $message = '') Assertions 165 Assertion assertLessThan($expected, $actual, $message = '') assertLessThanOrEqual($expected, $actual, $message = '') assertNotContains($needle, $haystack, $message = '', $ignoreCase = FALSE, $checkForObjectIdentity = TRUE) assertNotContainsOnly($type, $haystack, $isNativeType = NULL, $message = '') assertNotCount($expectedCount, $haystack, $message = '') assertNotEmpty($actual, $message = '') assertNotEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) assertNotInstanceOf($expected, $actual, $message = '') assertNotInternalType($expected, $actual, $message = '') assertNotNull($actual, $message = '') assertNotRegExp($pattern, $string, $message = '') assertNotSame($expected, $actual, $message = '') assertNotSameSize($expected, $actual, $message = '') assertNotTag($matcher, $actual, $message = '', $isHtml = TRUE) assertNull($actual, $message = '') assertObjectHasAttribute($attributeName, $object, $message = '') assertObjectNotHasAttribute($attributeName, $object, $message = '') assertRegExp($pattern, $string, $message = '') assertSame($expected, $actual, $message = '') assertSameSize($expected, $actual, $message = '') assertSelectCount($selector, $count, $actual, $message = '', $isHtml = TRUE) assertSelectEquals($selector, $content, $count, $actual, $message = '', $isHtml = TRUE) assertSelectRegExp($selector, $pattern, $count, $actual, $message = '', $isHtml = TRUE) assertStringEndsNotWith($suffix, $string, $message = '') assertStringEndsWith($suffix, $string, $message = '') assertStringEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = FALSE, $ignoreCase = FALSE) assertStringMatchesFormat($format, $string, $message = '') assertStringMatchesFormatFile($formatFile, $string, $message = '') assertStringNotEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = FALSE, $ignoreCase = FALSE) assertStringNotMatchesFormat($format, $string, $message = '') assertStringNotMatchesFormatFile($formatFile, $string, $message = '') assertStringStartsNotWith($prefix, $string, $message = '') assertStringStartsWith($prefix, $string, $message = '') assertTag($matcher, $actual, $message = '', $isHtml = TRUE) Assertions 166 Assertion assertThat($value, PHPUnit_Framework_Constraint $constraint, $message = '') assertTrue($condition, $message = '') assertXmlFileEqualsXmlFile($expectedFile, $actualFile, $message = '') assertXmlFileNotEqualsXmlFile($expectedFile, $actualFile, $message = '') assertXmlStringEqualsXmlFile($expectedFile, $actualXml, $message = '') assertXmlStringEqualsXmlString($expectedXml, $actualXml, $message = '') assertXmlStringNotEqualsXmlFile($expectedFile, $actualXml, $message = '') assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, $message = '') 167 Annexe B. Annotations Une annotation est une forme spéciale de méta donnée syntaxique qui peut être ajoutée au code source de certains langages de programmation. Bien que PHP n'ait pas de fonctionnalité dédiée à l'annotation du code source, l'utilisation d'étiquettes telles que annotation paramètres dans les blocs de documentation s'est établi dans la communauté PHP pour annoter le code source. En PHP, les blocs de documentation sont réflexifs: ils peuvent être accédés via la méthode de l'API de réflexivité getDocComment() au niveau des fonctions, classes, méthodes et attributs. Des applications telles que PHPUnit utilisent ces informations durant l'exécution pour adapter leur comportement. Cette annexe montre toutes les sortes d'annotations gérées par PHPUnit. author L'annotation author est un alias pour l'annotation group (voir la section intitulée « group ») et permet de filtrer des tests selon leurs auteurs. backupGlobals Les opérations de sauvegarde et de restauration des variables globales peuvent être complètement désactivées pour tous les tests d'une classe de cas de test comme ceci : /** * backupGlobals disabled */ class MonTest extends PHPUnit_Framework_TestCase { // ... } L'annotation backupGlobals peut également être utilisée sur les opérations de sauvegarde et de restauration : /** * backupGlobals disabled */ class MonTest extends PHPUnit_Framework_TestCase { /** * backupGlobals enabled */ public function testQuiIntergitAvecDesVariablesGlobales() { // ... } } backupStaticAttributes Les opérations de sauvegarde et de restauration pour les attributs statiques des classes peuvent être complètement désactivés pour tous les tests d'une classe de cas de test comme ceci : /** * backupStaticAttributes disabled */ class MonTest extends PHPUnit_Framework_TestCase { // ... Annotations 168 } L'annotation backupStaticAttributes peut également être utilisée au niveau d'une méthode de test. Ceci permet une configuration plus fine des opérations de sauvegarde et de restauration: /** * backupStaticAttributes disabled */ class MonTest extends PHPUnit_Framework_TestCase { /** * backupStaticAttributes enabled */ public function testQuiInteragitAvecDesAttributsStatiques() { // ... } } codeCoverageIgnore* Les annotations codeCoverageIgnore, codeCoverageIgnoreStart et codeCoverageIgnoreEnd peuvent être utilisées pour exclure des lignes de code de l'analyse de couverture. Pour la manière de les utiliser, voir la section intitulée « Ignorer des blocs de code ». covers L'annotation covers peut être utilisée dans le code de test pour indique quelle(s) méthode(s) un test veut tester: /** * covers CompteBancaire::getBalance */ public function testBalanceEstInitiallementAZero() { $this->assertEquals(0, $this->ba->getBalance()); } Si elle est fournie, seule l'information de couverture de code pour la(les) méthode(s) sera prise en considération. Tableau B.1, « Annotations pour indiquer quelles méthodes sont couvertes par un test » montre la syntaxe de l'annotation covers. Tableau B.1. Annotations pour indiquer quelles méthodes sont couvertes par un test Annotation Description covers NomClasse::nomMethode Indique que la méthode de test annotée couvre la méthode indiquée. covers NomClasse Indique que la méthode de test annotée couvre toutes les méthodes d'une classe donnée. covers NomClasse<extended> Indique que la méthode de test annotée couvre toutes les Annotations 169 Annotation Description méthodes d'une classe donnée ainsi que les classe(s) et interface(s) parentes. covers NomClasse::<public> Indique que la méthode de test annotée couvre toutes les méthodes publiques d'une classe donnée. covers NomClasse::<protected> Indique que la méthode de test annotée couvre toutes les méthodes protected d'une classe donnée. covers NomClasse::<private> Indique que la méthode de test annotée couvre toutes les méthodes privées d'une classe donnée. covers NomClasse::<!public> Indique que la méthode de test annotée couvre toutes les méthodes d'une classe donnée qui ne sont pas publiques. covers NomClasse::<!protected> Indique que la méthode de test annotée couvre toutes les méthodes d'une classe donnée qui ne sont pas protected. covers NomClasse::<!private> Indique que la méthode de test annotée couvre toutes les méthodes d'une classe donnée qui ne sont pas privées. coversNothing L'annotation coversNothing peut être utilisée dans le code de test pour indiquer qu'aucune information de couverture de code ne sera enregistrée pour le cas de test annoté. Ceci peut être utilisé pour le test d'intégration. Voir Exemple 14.3, « Un test qui indique qu'aucune méthode ne doit être couverte » pour un exemple. L'annotation peut être utilisée au niveau de la classe et de la méthode et sera surchargée par toute étiquette covers. dataProvider Une méthode de test peut accepter des paramètres arbitraires. Ces paramètres peuvent être fournis pas une méthode fournisseuse de données ( (provider() dans Exemple 4.4, « Utiliser un fournisseur de données qui renvoie un tableau de tableaux »). La méthode fournisseur de données peut être indiquée en utilisant l'annotation dataProvider. Voir la section intitulée « Fournisseur de données » pour plus de détails. depends PHPUnit gère la déclaration des dépendances explicites entre les méthodes de test. De telles dépendances ne définissent pas l'ordre dans lequel les méthodes de test doivent être exécutées mais Annotations 170 elles permettent de retourner l'instance d'une fixture de test par un producteur et de la passer aux consommateurs dépendants. Exemple 4.2, « Utiliser l'annotation depends pour exprimer des dépendances » montre comment utiliser l'annotation depends pour exprimer des dépendances entre méthodes de test. Voir la section intitulée « Dépendances des tests » pour plus de détails. expectedException Exemple 4.7, « Utiliser l'annotation expectedException » montre comment utiliser l'annotation expectedException pour tester si une exception est levée dans le code testé. Voir la section intitulée « Tester des exceptions » pour plus de détails. expectedExceptionCode L'annotation expectedExceptionCode, en conjonction avec expectedException permet de faire des assertions sur le code d'erreur d'une exception levée ce qui permet de cibler une exception particulière. class MonTest extends PHPUnit_Framework_TestCase { /** * expectedException MonException * expectedExceptionCode 20 */ public function testExceptionAUnCodeErreur20() { throw new MonException('Un message', 20); } } Pour faciliter les tests et réduire la duplication, un raccourci peut être utilisé pour indiquer une constante de classe comme un expectedExceptionCode en utilisant la syntaxe "expectedExceptionCode ClassName::CONST". class MonTest extends PHPUnit_Framework_TestCase { /** * expectedException MonException * expectedExceptionCode MaClasse::CODE_ERREUR */ public function testExceptionAUnCodeErreur20() { throw new MonException('Un message', 20); } } class MaClasse { const CODE_ERREUR = 20; } expectedExceptionMessage L'annotation expectedExceptionMessage fonctionne de manière similaire à expectedExceptionCode en ce qu'il vous permet de faire une assertion sur le message d'erreur d'une exception. Annotations 171 class MonTest extends PHPUnit_Framework_TestCase { /** * expectedException MonException * expectedExceptionMessage Un message */ public function testExceptionALeBonMessage() { throw new MonException('Un message', 20); } } Le message attendu peut être une partie d'une chaîne d'un message d'exception. Ceci peut être utile pour faire une assertion sur le fait qu'un nom ou un paramètre qui est passé s'affiche dans une exception sans fixer la totalité du message d'exception dans le test. class MonTest extends PHPUnit_Framework_TestCase { /** * expectedException MonException * expectedExceptionMessage cassé */ public function testExceptionALeBonMessage() { $param = "cassé"; throw new MonException('Paramètre "'.$param.'" incorrect.', 20); } } Pour faciliter les tests et réduire la duplication, un raccourci peut être utilisé pour indiquer une constante de classe comme un expectedExceptionCode en utilisant la syntaxe "expectedExceptionCode ClassName::CONST". Un exemple peut être trouvé dans la section intitulée « expectedExceptionCode ». group Un test peut être marqué comme appartement à un ou plusieurs groupes en utilisant l'annotation group comme ceci class MonTest extends PHPUnit_Framework_TestCase { /** * group specification */ public function testQuelquechose() { } /** * group regresssion * group bug2204 */ public function testAutreChose() { } } Des tests peuvent être sélectionnés pour l'exécution en se basant sur les groupes en utilisant les options --group et --exclude-group du lanceur de test en ligne de commandes ou en utilisant les directives respectives du fichier de configuration XML. Annotations 172 outputBuffering L'annotation outputBuffering peut être utilisée pour contrôler le tampon de sortie [http:// www.php.net/manual/en/intro.outcontrol.php] de PHP comme ceci /** * outputBuffering enabled */ class MonTest extends PHPUnit_Framework_TestCase { // ... } L'annotation outputBuffering peut également être utilisé au niveau de la méthode de test. Ceci permet un contrôle plus fin sur le tampon de sortie : /** * outputBuffering disabled */ class MonTest extends PHPUnit_Framework_TestCase { /** * outputBuffering enabled */ public function testQuiAfficheQuelqueChose() { // ... } } requires L'annotation requires peut être utilisée pour sauter des tests lorsque des pré-requis communs, comme la version de PHP ou des extensions installées, ne sont pas fournis. Une liste complète des possibilités et des exemples peuvent être trouvés à Tableau 9.3, « Usages possibles de requires » runTestsInSeparateProcesses runInSeparateProcess test Comme alternative à préfixer vos noms de méthodes de test avec test, vous pouvez utiliser l'annotation test dans le bloc de documentation d'une méthode pour la marquer comme méthode de test. /** Annotations 173 * test */ public function balanceInitialeDoitEtre0() { $this->assertEquals(0, $this->ba->getBalance()); } testdox ticket 174 Annexe C. Le fichier de configuration Configuration PHPUnit Les attributs d'un élément <phpunit> peuvent être utilisés pour configurer les fonctionnalités du coeur de PHPUnit. <phpunit backupGlobals="true" backupStaticAttributes="false" <!--bootstrap="/chemin/vers/amorce.php"--> cacheTokens="true" colors="false" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" forceCoversAnnotation="false" mapTestClassNameToCoveredClassName="false" printerClass="PHPUnit_TextUI_ResultPrinter" <!--printerFile="/chemin/vers/AfficheurResultat.php"--> processIsolation="false" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader" <!--testSuiteLoaderFile="/chemin/vers/ChargeurStandardDeSuiteDeTest.php"--> strict="false" verbose="false"> <!-- ... --> </phpunit> Le fichier de configuration XML ci-dessus correspond au comportement par défaut du lanceur de tests TextUI documenté dans la section intitulée « Options de la ligne de commandes ». Des options supplémentaires qui ne sont pas disponibles en tant qu'option de ligne de commandes sont : convertNoticesToExceptions, convertWarningsToExceptions, convertErrorsToExceptions Peuvent être utilisées pour désactiver la conversion automatique de toutes les erreurs, avertissement ou information de php en exception. forceCoversAnnotation La couverture de code ne sera enregistrée que pour les tests qui utilisent l'annotation covers documentée dans la section intitulée « covers ». Série de tests L'élément <testsuites> et son ou ses fils <testsuite> peuvent être utilisés pour composer une série de tests à partir des séries de test et des cas de test. <testsuites> <testsuite name="Ma suite de tests"> <directory>/chemin/vers/fichiers *Test.php</directory> <file>/chemin/vers/MonTest.php</file> <exclude>/chemin/a/exclure</exclude> </testsuite> Le fichier de configuration Configuration 175 </testsuites> En utilisant les attributs phpVersion et phpVersionOperator, une version requise de PHP peut être indiquée. L'exemple ci-dessous ne va ajouter que les fichiers /chemin/vers/*Test.php et /chemin/vers/MonTest.php si la version de PHP est au moins 5.3.0. <testsuites> <testsuite name="Ma suite de tests"> <directory suffix="Test.php" phpVersion="5.3.0" phpVersionOperator=">=">/chemin/ver <file phpVersion="5.3.0" phpVersionOperator=">=">/chemin/vers/MonTest.php</file> </testsuite> </testsuites> L'attribut phpVersionOperator est facultatif et vaut par défaut >=. Groupes L'élément <groups> et ses fils <include>, <exclude> et <group> peuvent être utilisés pour choisir des groupes de tests depuis une série de tests qui doivent (ou ne doivent pas) être exécutés. <groups> <include> <group>nom</group> </include> <exclude> <group>nom</group> </exclude> </groups> La configuration XML ci-dessus revient à appeler le lanceur de test TextUI avec les options suivantes: ? --group nom ? --exclude-group nom Inclure et exclure des fichiers de la couverture de code L'élément <filter> et ses fils peuvent être utilisés pour configurer les listes noires et les listes blanches pour les rapports de couverture de code. <filter> <blacklist> <directory suffix=".php">/chemin/vers/fichiers</directory> <file>/chemin/vers/fichier</file> <exclude> <directory suffix=".php">/chemin/vers/fichiers</directory> <file>/chemin/vers/fichier</file> </exclude> </blacklist> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">/chemin/vers/fichiers</directory> <file>/chemin/vers/fichier</file> <exclude> <directory suffix=".php">/chemin/vers/fichiers</directory> <file>/chemin/vers/fichier</file> </exclude> </whitelist> Le fichier de configuration Configuration 176 </filter> Journalisation L'élément <logging> et ses fils <log> peuvent être utilisés pour configurer la journalisation de l'exécution des tests. <logging> <log type="coverage-html" target="/tmp/report" charset="UTF-8" yui="true" highlight="false" lowUpperBound="35" highLowerBound="70"/> <log type="coverage-clover" target="/tmp/coverage.xml"/> <log type="coverage-php" target="/tmp/coverage.serialized"/> <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/> <log type="json" target="/tmp/logfile.json"/> <log type="tap" target="/tmp/logfile.tap"/> <log type="junit" target="/tmp/logfile.xml" logIncompleteSkipped="false"/> <log type="testdox-html" target="/tmp/testdox.html"/> <log type="testdox-text" target="/tmp/testdox.txt"/> </logging> La configuration XML ci-dessus revient à invoquer le lanceur de tests TextUI avec les options suivantes : ? --coverage-html /tmp/report ? --coverage-clover /tmp/coverage.xml ? --coverage-php /tmp/coverage.serialized ? --coverage-text ? --log-json /tmp/logfile.json ? > /tmp/logfile.txt ? --log-tap /tmp/logfile.tap ? --log-junit /tmp/logfile.xml ? --testdox-html /tmp/testdox.html ? --testdox-text /tmp/testdox.txt Les attributs charset, yui, highlight, lowUpperBound, highLowerBound, logIncompleteSkipped and showUncoveredFiles n'ont pas d'options équivalentes pour le lanceur de tests TextUI. ? charset: encodage de caractères à utiliser pour les pages html générées ? yui: améliore le rapport de couverture html en utilisant la bibliothèque yui. Par exemple, lorsque vous cliquez sur un numéro de ligne, un panneau YUI apparaît avec une liste de toutes les méthodes qui couvrent cette ligne. ? highlight: Quand mis à vrai, les rapports de couverture de code bénéficient de la coloration syntaxique. ? lowUpperBound: pourcentage de couverture maximum considérée comme étant faible. Le fichier de configuration Configuration 177 ? highLowerBound: pourcentage de couverture minimum considérée comme étant forte. ? showUncoveredFiles: Montre tous les fichiers en liste blanche dans la sortie --coverage-text et pas seulement ceux possédant des informations de couverture. Moniteurs de tests L'élément <listeners> et ses fils <listener> peuvent être utilisés pour brancher des moniteurs de tests additionnels lors de l'exécution des tests. <listeners> <listener class="MonMoniteur" file="/optionnel/chemin/vers/MonMoniteur.php"> <arguments> <array> <element key="0"> <string>Sebastian</string> </element> </array> <integer>22</integer> <string>April</string> <double>19.78</double> <null/> <object class="stdClass"/> </arguments> </listener> </listeners> La configuration XML ci-dessus revient à brancher l'objet $moniteur (voir ci-dessous) à l'exécution des tests : $moniteur = new MonMoniteur( array('Sebastian'), 22, 'April', 19.78, NULL, new stdClass ); Configurer les réglages de PHP INI, les constantes et les variables globales L'élément <php> et ses fils peuvent être utilisés pour configurer les réglages PHP, les constantes et les variables globales. Il peut également être utilisé pour préfixer l'include_path. <php> <includePath>.</includePath> <ini name="foo" value="bar"/> <const name="foo" value="bar"/> <var name="foo" value="bar"/> <env name="foo" value="bar"/> <post name="foo" value="bar"/> <get name="foo" value="bar"/> <cookie name="foo" value="bar"/> <server name="foo" value="bar"/> <files name="foo" value="bar"/> <request name="foo" value="bar"/> Le fichier de configuration Configuration 178 </php> La configuration XML ci-dessus correspond au code PHP suivant : ini_set('foo', 'bar'); define('foo', 'bar'); $GLOBALS['foo'] = 'bar'; $_ENV['foo'] = 'bar'; $_POST['foo'] = 'bar'; $_GET['foo'] = 'bar'; $_COOKIE['foo'] = 'bar'; $_SERVER['foo'] = 'bar'; $_FILES['foo'] = 'bar'; $_REQUEST['foo'] = 'bar'; Configurer les navigateurs pour Selenium RC L'élément <selenium> et ses fils <browser> peuvent être utilisés pour configurer une liste de serveurs Selenium RC. <selenium> <browser name="Firefox sur Linux" browser="*firefox /usr/lib/firefox/firefox-bin" host="ma.box.linux" port="4444" timeout="30000"/> </selenium> La configuration XML ci-dessus correspond au code PHP suivant : class WebTest extends PHPUnit_Extensions_SeleniumTestCase { public static $browsers = array( array( 'name' => 'Firefox sur Linux', 'browser' => '*firefox /usr/lib/firefox/firefox-bin', 'host' => 'ma.box.linux', 'port' => 4444, 'timeout' => 30000 ) ); // ... } 179 Annexe D. Index Index Symboles $backupGlobalsBlacklist, 79 $backupStaticAttributesBlacklist, 79 assert, 142 author, , 167 backupGlobals, 79, 167, 167 backupStaticAttributes, 79, 167, 168 codeCoverageIgnore, 137, 168 codeCoverageIgnoreEnd, 137, 168 codeCoverageIgnoreStart, 137, 168 covers, 135, 168 coversNothing, 136, 169 dataProvider, 9, 12, 12, 12, 169 depends, 8, 12, 12, 12, 169 expectedException, 12, 13, 170 expectedExceptionCode, 13, 170 expectedExceptionMessage, 13, 170 group, , , , 171 outputBuffering, 172, 172 requires, 172, 172 runInSeparateProcess, 172 runTestsInSeparateProcesses, 172 test, , 172 testdox, 173 ticket, 173 A Annotation, 7, , 8, 9, 12, 12, 12, 12, 13, , , , 135, 136, 137, 142, 167 anything(), arrayHasKey(), assertArrayHasKey(), 19, assertArrayNotHasKey(), 19, assertAttributeContains(), 22, assertAttributeContainsOnly(), 24, assertAttributeCount(), assertAttributeEmpty(), 26, assertAttributeEquals(), 29, assertAttributeGreaterThan(), 38, assertAttributeGreaterThanOrEqual(), 39, assertAttributeInstanceOf(), 40, assertAttributeInternalType(), 41, assertAttributeLessThan(), 44, assertAttributeLessThanOrEqual(), 45, assertAttributeNotContains(), 22, assertAttributeNotContainsOnly(), 24, assertAttributeNotCount(), assertAttributeNotEmpty(), 26, assertAttributeNotEquals(), 29, assertAttributeNotInstanceOf(), 40, assertAttributeNotInternalType(), 41, assertAttributeNotSame(), 51, Index 180 assertAttributeSame(), 51, assertClassHasAttribute(), 20, assertClassHasStaticAttribute(), 21, assertClassNotHasAttribute(), 20, assertClassNotHasStaticAttribute(), 21, assertContains(), 22, assertContainsOnly(), 24, assertContainsOnlyInstancesOf(), assertCount(), 25, assertEmpty(), 26, assertEquals(), 29, assertEqualXMLStructure(), 27, assertFalse(), 35, assertFileEquals(), 36, assertFileExists(), 37, assertFileNotEquals(), 36, assertFileNotExists(), 37, assertGreaterThan(), 38, assertGreaterThanOrEqual(), 39, assertInstanceOf(), 40, assertInternalType(), 41, Assertions, 2 assertJsonFileEqualsJsonFile(), 42, assertJsonFileNotEqualsJsonFile(), 42, assertJsonStringEqualsJsonFile(), 42, assertJsonStringEqualsJsonString(), 43, assertJsonStringNotEqualsJsonFile(), 42, assertJsonStringNotEqualsJsonString(), 43, assertLessThan(), 44, assertLessThanOrEqual(), 45, assertNotContains(), 22, assertNotContainsOnly(), 24, assertNotCount(), 25, assertNotEmpty(), 26, assertNotEquals(), 29, assertNotInstanceOf(), 40, assertNotInternalType(), 41, assertNotNull(), 46, assertNotRegExp(), 48, assertNotSame(), 51, assertNotSameSize(), assertNotTag(), 61, assertNull(), 46, assertObjectHasAttribute(), 47, assertObjectNotHasAttribute(), 47, assertPostConditions(), 76 assertPreConditions(), 76 assertRegExp(), 48, assertSame(), 51, assertSameSize(), assertSelectCount(), 53, assertSelectEquals(), 55, assertSelectRegExp(), 56, assertStringEndsNotWith(), 58, assertStringEndsWith(), 58, assertStringEqualsFile(), 59, assertStringMatchesFormat(), 49, assertStringMatchesFormatFile(), 50, Index 181 assertStringNotEqualsFile(), 59, assertStringNotMatchesFormat(), 49, assertStringNotMatchesFormatFile(), 50, assertStringStartsNotWith(), 60, assertStringStartsWith(), 60, assertTag(), 61, assertThat(), 63, assertTrue(), 65, assertXmlFileEqualsXmlFile(), 66, assertXmlFileNotEqualsXmlFile(), 66, assertXmlStringEqualsXmlFile(), 67, assertXmlStringEqualsXmlString(), 68, assertXmlStringNotEqualsXmlFile(), 67, assertXmlStringNotEqualsXmlString(), 68, attribute(), attributeEqualTo(), Avertissement PHP, 16 B Bouchon, 107 Bouchons, 140 C classHasAttribute(), classHasStaticAttribute(), Code Coverage, 138, 168 Composant dépendant, 107 Conception dirigée par le domaine, 127 Conception par contrat, 122 Configuration, , Constante, 177 contains(), Couverture de code, , , , , 133, 175 D Dépendances des tests, 7 Développement dirigé par le comportement, 127 Développement dirigé par les tests, 122, 127 Documentation agile, , , 139 Documentation générée automatiquement, 139 Documenter les hypothèses, 139 Doublure de test, 107 E Echec, 70 equalTo(), Erreur, 70 Erreur PHP, 16 expects(), 108, 109, 109, 110, 110, 111, 112, 112 Extreme Programming, 122, 127, 139 F fileExists(), Fixture, 75 G Générateur de squelette, 141 Index 182 Gestionnaire d'erreur, 16 getMock(), 108, 109, 110, 110, 111, 112, 112 getMockBuilder(), 109 getMockForAbstractClass(), 116 getMockFromWsdl(), 116 greaterThan(), greaterThanOrEqual(), Groupes de tests, , , , 175 H hasAttribute(), I identicalTo(), include_path, Indépendance des tests, 79 Interface souple, 107 isFalse(), isInstanceOf(), isNull(), isTrue(), isType(), J Journalisation, 154, 176 JSON, L lessThan(), lessThanOrEqual(), Liste blanche, 138, 175 Liste noire, 138, 175 Localisation des défauts, 8 Logfile, , logicalAnd(), logicalNot(), logicalOr(), logicalXor(), M matchesRegularExpression(), method(), 108, 109, 109, 110, 110, 111, 112, 112 Méthode canevas, 75, 76, 76, 76 Moniteurs de tests, 177 O Objet simulacre, 113, 114 onConsecutiveCalls(), 112 onNotSuccessfulTest(), 76 P php.ini, 177 PHPUnit_Extensions_RepeatedTest, 160 PHPUnit_Extensions_Selenium2TestCase, 146 PHPUnit_Extensions_SeleniumTestCase, 148 PHPUnit_Extensions_Story_TestCase, 127 PHPUnit_Extensions_TestDecorator, 160 Index 183 PHPUnit_Extensions_TestSetup, 160 PHPUnit_Framework_Assert, 125 PHPUnit_Framework_Error, 16 PHPUnit_Framework_Error_Notice, 17 PHPUnit_Framework_Error_Warning, 17 PHPUnit_Framework_IncompleteTest, 103 PHPUnit_Framework_IncompleteTestError, 103 PHPUnit_Framework_Test, 161 PHPUnit_Framework_TestCase, 7, 158 PHPUnit_Framework_TestListener, , 159, 177 PHPUnit_Runner_TestSuiteLoader, PHPUnit_Util_Printer, Processus indépendants, Programmation en testant d'abord, 122 R Rapport, Refactorisation, 120 Remarque PHP, 16 returnArgument(), 109 returnCallback(), 111 returnSelf(), 110 returnValue(), 108, 109 returnValueMap(), 110 S Selenium RC, 178 Selenium Server, 146 Série de tests, 174 setUp(), 75, 76, 76 setUpBeforeClass, 78 setUpBeforeClass(), 76, 76 stringContains(), stringEndsWith(), stringStartsWith(), Suite de tests, 81 Système à tester, 107 T tearDown(), 75, 76, 76 tearDownAfterClass, 78 tearDownAfterClass(), 76, 76 Test incomplet, 103, 141 Test unitaire, 122 TestDox, 139, 173 Tests automatisés, 2 Tests dirigés par les données, 161 Tests indépendants, , , Tests unitaires, 1 throwException(), 112 V Variable globale, 177 Variables globales, 79 W will(), 108, 109, 109, 110, 110, 111, 112, 112 Index 184 X Xdebug, 133 XML Configuration, 82 185 Annexe E. Bibliographie [Astels2003] Test Driven Development. David Astels. Copyright © 2003. Prentice Hall. ISBN 0131016490. [Astels2006] A New Look at Test-Driven Development. David Astels. Copyright © 2006. http:// blog.daveastels.com/files/BDD_Intro.pdf. [Beck2002] Test Driven Development by Example. Kent Beck. Copyright © 2002. Addison-Wesley. ISBN 0-321-14653-0. [Meszaros2007] xUnit Test Patterns: Refactoring Test Code. Gerard Meszaros. Copyright © 2007. Addison- Wesley. ISBN 978-0131495050. 186 Annexe F. Copyright Copyright (c) 2005-2012 Sebastian Bergmann. Cette oeuvre est soumise à la licence Creative Commons Attribution 3.0 non transposée. Un résumé de la licence est donné ci-dessous, suivi de la version intégrale. -------------------------------------------------------------------- Vous êtes libre de : * partager - reproduire, distribuer et communiquer l'oeuvre * adapter - adapter l'oeuvre Selon les conditions suivantes: Attribution. Vous devez attribuer l'oeuvre de la manière indiquée par l'auteur de l'oeuvr * A chaque réutilisation ou distribution de cette oeuvre, vous devez faire apparaître clairement au public la licence selon laquelle elle est mise à disposition. La meilleure manière de l'indiquer est un lien vers cette page web. * N'importe laquelle des conditions ci-dessus peut être waived si vous avez l'autorisation du titulaire de droits. * Rien dans cette licence ne contrevient ou ne restreint les droits moraux de l'auteur. Votre droit à l'utilisation équitable et vos autres droits ne sont en aucune manière affectés par ce qui précède. Ceci est le résumé explicatif "lisible par les humains" du Code Juridique (la version int ==================================================================== Creative Commons Legal Code Attribution 3.0 non transposée Creative Commons n'est pas un cabinet d'avocats et ne fournit pas de services de conseil Contrat THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other Copyright 187 alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. c. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. d. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. e. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. f. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three- dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered Copyright 188 a literary or artistic work. g. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. h. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. i. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, d. to Distribute and Publicly Perform Adaptations. e. For the avoidance of doubt: i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; Copyright 189 ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. b. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv), consistent with Section 3(b), in the case of an Copyright 190 Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. c. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or Copyright 191 entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. Copyright 192 Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. Creative Commons peut être contacté à http://creativecommons.org/. ====================================================================

PARTAGER SUR

Envoyer le lien par email
2518
READS
30
DOWN
0
FOLLOW
5
EMBED
DOCUMENT # TAGS
#php  #unit test in php 

licence non indiquée


DOCUMENT # INDEX
PHP 
img

Partagé par  Aguirre

 Suivre

Auteur:
Source:Non communiquée