web-dev-qa-db-fra.com

Meilleures pratiques PHPUnit pour organiser les tests

Je vais actuellement partir de zéro avec les tests phpunit d'un projet. J'ai donc étudié certains projets (comme Zend) pour voir comment ils font les choses et comment ils organisent leurs tests.

La plupart des choses sont assez claires, la seule chose avec laquelle j'ai des problèmes est d'organiser correctement les suites de tests. Zend a un AllTests.php à partir duquel se charge d'autres suites de tests.
Regard difficile sur la classe qu'il utilise PHPUnit_Framework_TestSuite pour créer un objet suite, puis y ajouter les autres suites, mais si je regarde dans les documents PHPUnit pour organiser les tests dans les versions PHPUnit après 3.4, il n'y a qu'une description pour XML ou FileHierarchy. Celui qui utilise des classes pour organiser les tests a été supprimé.
Je n'ai rien trouvé que cette méthode soit obsolète et des projets comme Zend l'utilisent toujours.

Mais s'il est obsolète, comment pourrais-je organiser des tests dans la même structure avec la configuration xml? L'exécution de tous les tests n'est pas un problème, mais comment organiser les tests (dans le xml) si je voulais seulement exécuter quelques tests. Peut-être créer plusieurs xml où je ne spécifie que quelques tests/suites de tests à exécuter?

Donc, si je voulais tester uniquement module1 et module2 de l'application, aurais-je un xml supplémentaire pour chacun et définissant des suites de tests uniquement pour ces modules (classes utilisées par le module). Et aussi une qui définit une suite de tests pour tous les tests?

Ou serait-il préférable d'utiliser le @group annotation sur les tests spécifiques pour les marquer comme étant pour module1 ou module2?

Merci d'avance de m'avoir indiqué quelques bonnes pratiques.

66
enricog

Je commencerai par un lien vers le manuel, puis j'entrerai dans ce que j'ai vu et entendu sur le terrain.

Organisation des suites de tests phpunit

Organisation du module/dossier de test dans le système de fichiers

Mon approche recommandée consiste à combiner le système de fichiers avec une configuration xml.

tests/
 \ unit/
 | - module1
 | - module2
 - integration/
 - functional/

avec un phpunit.xml avec un simple:

<testsuites>
  <testsuite name="My whole project">
    <directory>tests</directory>
  </testsuite>
</testsuites>

vous pouvez diviser les suites de tests si vous le souhaitez, mais c'est un projet à choisir.

L'exécution de phpunit exécutera alors TOUS les tests et exécutera phpunit tests/unit/module1 exécutera tous les tests du module1.

Organisation du dossier "unit"

L'approche la plus courante consiste à refléter votre source/ structure du répertoire dans votre tests/unit/ structure des dossiers.

Vous avez de toute façon une TestClass par ProductionClass, c'est donc une bonne approche dans mon livre.

Organisation des fichiers

  • Une classe par fichier.

Cela ne fonctionnera pas de toute façon si vous avez plus d'une classe de test dans un fichier, alors évitez cet écueil.

  • Ne pas avoir d'espace de noms de test

Cela rend l'écriture du test plus verbeuse car vous avez besoin d'une instruction d'utilisation supplémentaire, donc je dirais que la classe de test devrait aller dans le même espace de noms que la classe de production, mais ce n'est rien que PHPUnit vous oblige à faire. Je viens de trouver que c'était plus facile sans inconvénients.

Exécuter seulement quelques tests

Par exemple phpunit --filter Factory exécute tous les FactoryTests pendant que phpunit tests/unit/logger/ exécute tout ce qui concerne la journalisation.

Vous pouvez utiliser @group balises pour quelque chose comme des numéros de problème, des histoires ou quelque chose, mais pour les "modules", j'utiliserais la disposition des dossiers.

Plusieurs fichiers xml

Il peut être utile de créer plusieurs fichiers xml si vous souhaitez avoir:

  • un sans couverture de code
  • un juste pour les tests unitaires (mais pas pour les tests fonctionnels ou d'intégration ou de longue durée)
  • autres cas courants de "filtre"
  • PHPBB3, par exemple, fait cela pour their phpunit.xmls

Couverture du code pour vos tests

Comme cela est lié au démarrage d'un nouveau projet avec des tests:

  • Ma suggestion est d'utiliser @covers tags comme décrit dans mon blog (Uniquement pour les tests unitaires, toujours couvrir toutes les fonctions non publiques, toujours utiliser des tags de couvertures.
  • Ne générez pas de couverture pour vos tests d'intégration. Cela vous donne un faux sentiment de sécurité.
  • Utilisez toujours la liste blanche pour inclure tout votre code de production afin que les chiffres ne vous mentent pas!

Chargement automatique et amorçage de vos tests

Vous n'avez besoin d'aucune sorte de chargement automatique pour vos tests. PHPUnit s'en chargera.

Utilisez le <phpunit bootstrap="file"> attribut pour spécifier votre bootstrap de test. tests/bootstrap.php est un bel endroit pour le dire. Là, vous pouvez configurer le chargeur automatique de vos applications, etc. (ou appeler vos applications bootstrap d'ailleurs).

Sommaire

  • Utilisez la configuration xml pour à peu près tout
  • Tests unitaires et d'intégration séparés
  • Vos dossiers de tests unitaires doivent refléter la structure de vos dossiers d'applications
  • Pour exécuter uniquement des tests spécifiques, utilisez phpunit --filter ou phpunit tests/unit/module1
  • Utilisez le mode strict dès le départ et ne le désactivez jamais.

Exemples de projets à examiner

104
edorian

Structure de répertoire de base:

J'ai essayé de garder le code de test juste à côté du code testé, littéralement dans le même répertoire avec un nom de fichier légèrement différent du fichier avec le code qu'il teste. Jusqu'à présent, j'aime cette approche. L'idée est que vous n'avez pas à consacrer du temps et de l'énergie à synchroniser la structure du répertoire entre votre code et votre code de test. Donc, si vous changez le nom du répertoire dans lequel se trouve le code, vous n'avez pas non plus besoin d'aller chercher et de changer le nom du répertoire pour le code de test. Cela vous fait également passer moins de temps à chercher le code de test qui va avec du code car il est juste à côté. Cela rend même moins compliqué de créer le fichier avec le code de test pour commencer parce que vous n'avez pas besoin de trouver d'abord le répertoire avec les tests, éventuellement créer un nouveau répertoire pour correspondre à celui pour lequel vous créez des tests, et puis créez le fichier de test. Vous venez de créer le fichier de test ici.

Un avantage énorme de cela est que cela signifie que les autres employés (pas vous parce que vous ne feriez jamais cela) seront moins susceptibles d'éviter d'écrire du code de test pour commencer parce que c'est tout simplement trop de travail. Même s'ils ajoutent des méthodes aux classes existantes, ils seront moins susceptibles de ne pas avoir envie d'ajouter des tests au code de test existant, en raison de la faible friction de trouver le code de test.

Un inconvénient est qu'il est plus difficile de publier votre code de production sans les tests qui l'accompagnent. Bien que si vous utilisez des conventions de dénomination strictes, cela pourrait toujours être possible. Par exemple, j'utilise ClassName.php, ClassNameUnitTest.php et ClassNameIntegrationTest.php. Lorsque je veux exécuter tous les tests unitaires, il existe une suite qui recherche les fichiers se terminant par UnitTest.php. La suite de tests d'intégration fonctionne de manière similaire. Si je le voulais, je pourrais utiliser une technique similaire pour éviter que les tests ne soient mis en production.

Un autre inconvénient de cette approche est que lorsque vous recherchez simplement du code réel, et non du code de test, il faut un peu plus d'efforts pour différencier les deux. Mais je pense que c'est en fait une bonne chose car cela nous oblige à ressentir la douleur de la réalité que le code de test est aussi du code, il ajoute ses propres coûts de maintenance et fait tout aussi vitalement partie du code que toute autre chose, pas juste quelque chose sur le côté quelque part.

ne classe de test par classe:

C'est loin d'être expérimental pour la plupart des programmeurs, mais c'est pour moi. J'expérimente avec seulement une classe de test par classe testée. Dans le passé, j'avais un répertoire complet pour chaque classe testée, puis j'avais plusieurs classes dans ce répertoire. Chaque classe de test a configuré la classe en cours de test d'une certaine manière, puis a eu un tas de méthodes chacune avec une assertion différente. Mais alors j'ai commencé à remarquer certaines conditions dans lesquelles j'obtiendrais ces objets avaient des choses en commun avec d'autres conditions dans lesquelles ils étaient entrés dans d'autres classes de test. La duplication devient trop lourde à gérer, j'ai donc commencé à créer des abstractions pour la supprimer. Le code de test est devenu très difficile à comprendre et à maintenir. Je m'en suis rendu compte, mais je ne voyais pas d'alternative qui avait du sens pour moi. Le simple fait d'avoir une classe de test par classe semblait ne pas pouvoir tester presque assez de situations sans devenir accablant d'avoir tout ce code de test dans une classe de test. Maintenant, j'ai une perspective différente à ce sujet. Même si j'avais raison, c'est un énorme amortisseur pour les autres programmeurs, et moi-même, voulant écrire et maintenir les tests. Maintenant, j'expérimente à me forcer à avoir une classe de test par classe testée. Si je rencontre trop de choses à tester dans cette classe de test, j'expérimente à voir cela comme une indication que la classe testée en fait trop et devrait être divisée en plusieurs classes. Pour supprimer la duplication, j'essaie de m'en tenir autant que possible à des abstractions plus simples qui permettent à tout d'exister dans une classe de test lisible.

UPDATE J'utilise et j'aime toujours cette approche, mais j'ai trouvé une très bonne technique pour réduire la quantité de code de test et la quantité de duplication. Il est important d'écrire des méthodes d'assertion réutilisables à l'intérieur de la classe de test elle-même qui sont fortement utilisées par les méthodes de test de cette classe. Cela m'aide à trouver les bons types de méthodes d'assertion si je les considère comme des DSL internes (quelque chose qu'Oncle Bob promeut, eh bien, il promeut en fait de faire des DSL internes). Parfois, vous pouvez pousser ce concept DSL encore plus loin (en fait créer un DSL) en acceptant un paramètre de chaîne qui a une valeur simple qui fait référence au type de test que vous essayez d'effectuer. Par exemple, j'ai créé une fois une méthode d'assertion réutilisable qui acceptait un paramètre $ left, $ comparesAs et $ right. Cela a rendu les tests très courts et lisibles car le code lisait quelque chose comme $this->assertCmp('a', '<', 'b').

Honnêtement, je ne saurais trop insister sur ce point, c'est tout le fondement de la création de tests d'écriture qui est durable (que vous et les autres programmeurs voulez continuer à faire). Il permet à la valeur que les tests ajoutent à plus que ce qu'ils enlèvent. Le fait n'est pas que vous devez utiliser cette technique exacte, le fait est que vous devez utiliser une sorte d'abstraction réutilisable qui vous permet d'écrire des tests courts et lisibles. Il peut sembler que je sors du sujet de la question, mais je ne le suis vraiment pas. Si vous ne le faites pas, vous finirez par tomber dans le piège d'avoir à créer plusieurs classes de test par classe en cours de test, et les choses se décomposent vraiment à partir de là.

1
still_dreaming_1