PHPUnit : les limites du data provider standard

PHPUnit et les data providers

Si vous lisez ceci, je suppose qu’il n’y a nul besoin de revenir sur ce que sont les tests unitaires, notamment ici en PHP avec PHPUnit. Je suppose également que vous connaissez les data providers. Ceux-ci permettant via une fonction de fournir à une fonction de tests un jeu de données.

L’intérêt d’un data provider

L’intérêt d’utiliser un data provider vous ait également probablement déjà connu : en chargeant un jeu de tests via une fonction externe, nous obtenons deux avantages importants :

  1. D’une part cela permet de gagner en lisibilité en externalisant toutes les données du test pour ne garder dans notre fonction que les éléments liés à notre pattern AAA : Arrange, Act, Assert.
  2. D’autre part, l’utilisation du data provider, permet de rejouer X fois le test, en prenant en compte tous les cas de figure pouvant possiblement se produire.

La couverture du test est donc optimale et claire pour le développeur.

Utilisation basique de PHPUnit avec un data provider

Exemple d’un jeu de données basique tel qu’on peut le voir dans les tutoriels

Les limites d’un data provider

Le data provider fonctionne très bien dès lors que la fonction en entrée prend des paramètres “simples”, c’est-à-dire des valeurs numériques ou des petites chaînes de caractères.

Dans les cas plus complexes où la fonction manipule de grands tableaux PHP, traite du XML ou bien encore des objets, le gain initial, en lisibilité en externalisant les données, est toujours d’actualité dans la fonction de test. Mais cela n’est plus le cas au niveau de la classe de test ! En effet, on arrive avec des tailles de fichier rapidement importante et des jeux de données de très grandes tailles.

Classe de tests comportant un data provider chargeant des objets

Exemple d’une ligne d’un jeu de données complexe faisant déjà 3 à 4 fois plus de ligne que le test.

Quelles perspectives d’évolution ?

Dès lors que faire afin de garder des classes de tests simples et claires ? La solution la plus logique serait d’externaliser au sein de fichiers externes les jeux de données. C’est la solution que je vais développer et héberger sur mon GitHub lors des prochaines semaines.

Le cahier des charges

Afin de garder une cohérence avec la logique PHPUnit et un rangement efficace des fichiers, cet élément devra respecter les choses suivantes :

  • être automatiquement chargé via l’attribut PHPdoc @dataProvider
  • 1 fonction = un fichier “data set”
  • les jeux de données seront correctement rangés, par exemple dans un dossier “data/” dans le répertoire de tests et rangés également par classe.
  • les jeux de données pourront être au format PHP (via un array) ou en json.

La solution proposée

La solution que je propose, serait donc une classe intermédiaire entre les classes de tests et la classe “PHPUnit_Framework_TestCase”. Ainsi, la fonctionnalité serait ajoutée de manière transparente tout en préservant le fonctionnement standard de PHPUnit.

Ensuite, au sein des classes de tests, un provider générique (un pour le json, un pour le php) serait utilisé. Celui-ci serait en fait une simple fonction allant automatiquement chercher et charger les données de tests, en récupérant le fichier au sein d’un répertoire :

./data/<nom de la classe de test>/<nom de la fonction de test

Ainsi, pour peu que cette classe implémentant les “data providers génériques” soit automatiquement chargée via le bootstrap défini dans “phpunit.xml”, les modifications pour un développeur seraient minimes, facilitant l’utilisation de cette solution.

Publié le 15 août 2017 par Edouard dans PHPUnit