Pour répondre à des problématiques de projet, les “tech” vont concevoir des solutions/réponses et les mettre en place au sein de leur projet. Ces nouvelles pratiques mises en place dans le projet vont être partagées avec les autres et avec les futurs intervenants, notamment par le biais d’une documentation.

Cependant, il est compliqué de s’assurer que chacun respecte ces pratiques ou les applique correctement.

On peut alors étendre l’utilisation des tests unitaires codés pour vérifier que certaines pratiques sont bien mises en place et continuent à être utilisées au fil de l’évolution du projet.

1. Objectifs d’un DTO

Comme vu précédemment, un DTO est une classe qui doit répondre à ces contraintes :

  • La seule utilisation de l’objet est de récupérer les données qu’il transporte.
  • Ces données doivent être immuables, c’est-à-dire en lecture seule.
  • L’objet ne doit pas mettre à disposition des méthodes publiques.
    Si le besoin est de transformer les données, il faut utiliser un autre type d’objet, comme un DataTransformer.

Avec les évolutions de PHP, il est possible de répondre à ces contraintes par le biais des éléments suivants :

  • Les propriétés doivent utiliser le mot-clef readonly, ce qui les rend immuables ; elles ne peuvent être modifiées qu’à l’instanciation de l’objet (dans le constructeur).
  • Les propriétés doivent être publiques, ce qui permet de les récupérer sans avoir à utiliser de méthode.

Une fois ces règles définies, on peut s’assurer de leur mise en place à travers des tests unitaires et les ReflectionClass de PHP, qui permettent de récupérer des informations sur une classe. C’est grâce à ces informations que l’on va pouvoir mettre en place des tests qui vérifient que les règles de développement sont bien respectées.

Lien vers la documentation PHP des ReflectionClass.

1.1 Tester les propriétés

Vous allez compléter vos tests unitaires sur le MediaDto afin de vérifier que toutes les propriétés de la classe sont définies comme readonly et public.

Ce test devrait alors vous ajouter 4 x 2 assertions (4 propriétés, 2 assertions différentes).

Rajoutez maintenant une propriété privée dans le DTO et une propriété publique, mais pas en lecture seule (il faudra faire quelques ajustements sur la classe pour pouvoir le faire).

Profitez-en pour supprimer vos tests sur le constructeur, qui ne sont plus utiles.

Vérifiez que phpunit remonte deux failures. S’il n’y en a qu’une, vous devriez sûrement utiliser un Data Provider plutôt qu’une boucle foreach dans votre test…

Faites un revert de vos modifications sur la classe MediaDto afin que vos tests soient à nouveau au vert.

1.2 Tester les méthodes

Vous allez compléter vos tests unitaires sur le MediaDto afin de vérifier que la classe ne met à disposition aucune méthode définies comme public.

⚠️ Le constructeur est une méthode définie comme public, elle doit être autorisée dans ce test.
Lorsque vous êtes dans ce cas de figure, utilisez l’instruction $this->addToAssertionCount(count: 1); afin d’indiquer qu’une assertion a été faite.

Ce test devrait alors vous ajouter une assertion supplémentaire.

Rajoutez maintenant trois méthodes dans votre DTO pour public, protected et private. Vérifiez que phpunit rajoute trois assertions supplémentaires dont une en échec.

Faites un revert de vos modifications sur la classe MediaDto afin que vos tests soient à nouveau au vert.

1.3. Tester tous les DTOs

Vous avez écrit des tests pour une classe DTO, ce qui signifie qu’il faudrait faire la même chose pour toutes les classes DTO du projet. Par ailleurs, vous devez vous assurer que chaque nouveau DTO aura des tests unitaires.

Cela semble être une tâche répétitive et fastidieuse, qui a de fortes probabilités d’être oubliée au fil du temps.

Vous allez donc créer un test applicable à l’ensemble des classes DTO du projet, c’est-à-dire toutes les classes correspondant au pattern *Dto.php et incluses dans le namespaces TroisOlen\PhpunitTp\Model (ou dans le dossier src/Model/).

Vous devriez alors avoir trois tests pour les méthodes (correspondant au constructeur des trois DTOs existants) et neuf tests (soit dix-huit assertions) pour les propriétés (trois pour le AnswerDto, deux pour le QuoteDto et quatre pour le MediaDto).

Une fois ces tests écrits et valides, vous pouvez supprimer la classe de test sur MediaDto, qui a été remplacée par vos nouveaux tests.

2. Objectifs d’un AbstractStaticFactory

Le projet met à dispositions des classes Factory en se basant sur le design pattern éponyme.

Une “factory” est une classe permettant d’instancier un object d’une classe précise, en fonction de paramètres donnés. L’objectif de cette classe est de centraliser la création d’objects complexes afin de ne pas avoir besoin de dupliquer du code ou à utiliser des constructeurs statiques directement dans la classe concernée.

Ainsi une classe FooFactory permettra d’instancier des objects de la classe Foo.

Les factories peuvent être des services (c’est-à-dire des classes instanciées une seule fois et injectées en tant que dépendances dans d’autres classes) ou des classes “statiques” (c’est-à-dire des classes qui ne sont pas instanciées et dont les méthodes sont appelées directement).

C’est justement ce dernier cas qui est mis en pratique dans le projet à travers la classe abstraite AbstractStaticFactory qui s’assure que les classes qui en héritent ne puissent pas être instanciées en rendant le constructeur private (non appelable en dehors de la classe, ou dans les classes filles), final (non surchargeable dans les classes filles) et levant une exception.

Selon les mêmes concepts que précédemment, vous allez utiliser les tests unitaires pour vérifier que les classes Factory héritant de la AbstractStaticFactory n’aient aucune méthode public qui ne soit pas static ET un constructeur private et final.

2.1 Tester les méthodes

Vous allez écrire un test pour la classe MediaTypeEnumFactory qui vérifie les conditions suivantes sur l’ensemble de ses méthodes :

  • Le constructeur est private et final.
  • Les méthodes public à disposition sont toutes static et qu’il y en ait au moins une.

Une fois ces tests prêts, vérifiez le rapport de PHPUnit et rajoutez une méthode public non static, une méthode protected et une méthode private. Vous devriez alors avoir un test en “FAILED”.

Faites un revert des modifications de la classe MediaTypeEnumFactory pour que vos tests passent en “OK”.

ℹ️ Il ne sera pas possible de tester la surcharge du constructeur ou l’appel au constructeur dans la classe factory puisque les mots-clefs final et private lèvent une erreur PHP à l’exécution.

2.2 Tester toutes les factories héritant de AbstractStaticFactory

Comme précédemment avec les DTO, les tests que vous venez d’écrire ne s’appliquent qu’à une seule factory or, nous aimerions qu’il y ait que ces tests soient communs à toutes les factories concernées.

Faites en sorte de récupérer toutes les classes *Factory présentent dans le namespace TroisOlen\Phpunit\Factory (ou dans le dossier src/Factory/) et héritant de la classe AbstractStaticFactory, puis de réaliser les tests que vous avez définis.

Une fois ces tests écrits et valides, vous pouvez supprimer ceux de la classe MediaTypeEnumFactory. Faites attention à ne pas supprimer les tests sur la méthode getFromConverter() qui sont toujours utiles et pertinents.

Retour aux concepts de base

Avec la “Réflexion”, vous obtenez des tests plus poussés et pouvant même répondre à des problématiques particulières, voire bloquantes dans certains cas. Il s’agit quand même d’une utilisation avancée de PHP et de PHPUnit et vous allez retrouver quelques concepts de base, à savoir les “doublures” (🇬🇧 doubles).