Stratégie d'écriture des tests pour couvrir au mieux les fonctionnalités¶
Contexte¶
Depuis tôt dans la vie du projet on utilise innmind/black-box pour générer aléatoirement les données qu'on ingère dans l'API pour vérifier que l'on supporte de partout tout un jeu de caractères qu'on souhaite supporter.
Suite à une expérimentation dans le projet Efalia Safe on utilise cet outil également pour générer des tests pré-paramétrés pour pouvoir les utiliser en dépendance d'autres tests avant de faciliter l'initialisation de l'état nécessaire à ces nouveaux tests.
seuls les tests positifs ont un intérêt à être réutilisés
Pour faciliter la réutilisation des tests il nous faut une organisation claire pour pouvoir trouver ceux qui nous intéressent.
Décision¶
Un même test positif doit couvrir les différentes variations autorisées par l'input de l'API, du moment que la variation ne dépend pas d'une entité tierces de l'API. Cela veut donc dire qu'on doit avoir :
CreerBannette::any()qui abstraitEither(CreerBannette::avecConteneur(), CreerBannette::sansConteneur())ClasserDirectement::any()ClasserDirectementDansUnDossier::any()
Pour la bannette on n'a qu'un test car le conteneur ne représente "rien" pour notre API (on s'assure uniquement qu'il s'agit d'un uuid), par contre pour le classement direct on sépare en 2 car on a besoin d'un dossier.
Cette structure des tests doit nous influencer dans l'ajout de fonctionnalités à l'API:
- On ne rajoute des champs optionnels sur un endpoint existant que s'il ne s'agit pas d'une référence à une entité tierce. Cela implique que l'ajout de cette variation dans le test existant sera répercuté dans tous les autres tests qui dépendent de celui-ci.
Découplage entre les tests¶
Pour éviter de trop coupler un test à ceux dont il dépend pour générer son état initial (car potentiellement la structure définie au dessus peut être amenée à changer) il faudrait passer par une indirection.
Autant on couvre tous les cas en utilisant directement CreerBannette::any() par contre pour utiliser un document à la racine d'une armoire ou dans un dossier il faut manuellement faire Either(ClasserDirectement::any(), ClasserDirectementDansUnDossier::any()) de partout.
Cette indirection serait à utiliser de cette façon :
Entites::bannette(); // équivalent de CreerBannette::any()
Entites\Document::àLaRacineDuneArmoire(); // équivalent de ClasserDirectement::any()
Entites\Document::dansUnDossier(); // équivalent de ClasserDirectementDansUnDossier::any()
Entites\Document::any(); // équivalent de Either(ClasserDirectement::any(), ClasserDirectementDansUnDossier::any())
ici on ne mentionne qu'un document classé dans un dossier ou à la racine d'une armoire mais il existe d'autres configurations
Cette approche devrait également nous permettre d'établir un vocabulaire simplifié et décorélé de toute implémentation pour permettant de comprendre rapidement quels sont les différents cas d'usage de l'application.