Quels sont les 7 antipatrons des tests automatisés ?

Parfois, il est plus facile d’apprendre de « ce qu’il ne faut pas faire » plutôt que de « ce qu’il faut faire ». Les antipatrons représentent les mauvaises pratiques courantes, les « Code smells » et les pièges lors de la création de tests automatisés. Vous devez les connaitre pour pouvoir les repérer. Voici les 7 antipatrons des tests automatisés.

Fail test

Source image : fr.vecteezy.com

1. Pas de structure claire dans le test

Ne pas suivre une structure attendue dans chaque test rend sa maintenance difficile.

Les tests sont une documentation de code vivante, donc tout le monde devrait pouvoir les lire / les modifier rapidement.

Il existe un modèle pour structurer le code dans les méthodes de test : les 3 A « Arrange, Act, Assert ».

Il faut organiser votre code en phases, par souci de clarté. Il peut être mappé à « Setup, Exercise, Verify, Teardown » dans le modèle de test à quatre phases et à « Given, When, Then » dans BDD. Vous pouvez également consulter notre article sur l’utilisation de la méthode BDD dans les projets agiles.

  • Disposer / Donner / Configurer : initialisation du SUT (système sous test, tout ce que vous testez) et d’autres dépendances comme les simulacres et les faux.
  • Act / Quand / Exercice : l’appel à la méthode testée – idéalement une seule ligne.
  • Assert / Then / Verify: affirmations de la sortie ou vérifications des appels effectués.
  • (Facultatif) Démontage : s’il y a des ressources à libérer.

En conséquence, il y a généralement 3 phases séparées visuellement par des lignes vides.

Si vous répondez : « Mais j’ai beaucoup de code dans mon test ! J’ai besoin de plus de 2 lignes vides ! », on vous rétorquerait « c’est probablement une affaire de design ».

Vous devriez peut-être diviser votre SUT, envisager de créer un « DSL local » (méthodes d’assistance de haut niveau) ou peut-être extraire la création SUT à une méthode d’usine.

2. « Refactoring » lors de l’ajout d’une fonctionnalité

L’ajout d’une nouvelle fonctionnalité comporte des risques et génère du stress, même lorsque vous faites du TDD. Alors, pourquoi aggraver les choses en corrigeant cela ou en améliorant cela simultanément ? Cela peut conduire à une explosion de cas non testés et de bugs difficiles à repérer, même si le pire est l’anxiété que cela entraîne.

Donc, l’essentiel c’est de se détendre et se concentrer sur la fonctionnalité à portée de main. Il est vrai que vous devriez laisser le code dans un meilleur état que celui que vous avez trouvé mais faites-le :

  • Avant la fonctionnalité (si cela aide à l’implémenter)
  • Ou bien Après la fonctionnalité : dans ce cas, il faut noter les idées de refactoring sur une feuille de papier, comme TO DO’s dans le code, dans les tâches / histoires de votre tracker, pour réduire la « peur d’oublier ».

3. Modifier l’implémentation pour rendre les tests possibles

Antipatrons dans les « tests unitaires »: le mot unitaire représente un élément d’une interface publique. Les méthodes privées sont un détail de mise en œuvre (auxiliaires des méthodes publiques) et sont à tester indirectement ! Il n’y a probablement aucune exception à cette règle. Si vous avez du mal à tester toutes les possibilités de méthodes privées, vous avez peut-être un « god object » que vous devriez diviser en plusieurs composants.

« Ah… mais je peux rendre la méthode publique ! ». C’est encore pire… vous ne devez pas modifier l’implémentation du code uniquement pour rendre les tests possibles. Tester des méthodes privées n’est qu’un exemple, mais il y a bien plus encore. Cela signifie généralement qu’il y a quelque chose qui cloche dans le processus.

4. Les antipatrons dans la difficulté d’écriture du test

Souvent, un test est difficile à écrire car vous êtes nouveau sur ce problème et que vous devez vous familiariser avec ce dernier. Dans ce cas, essayez d’expliquer le problème à quelqu’un (même à un canard) ou bien, faites une pause pour organiser vos idées.

Si vous utilisez TDD, vous pouvez essayer le test de démarrage (un test très basique) ou le test explicatif (celui que vous utiliseriez pour expliquer à quelqu’un).

Cependant, si le test finit par être trop compliqué, trop long (beaucoup de code de configuration par exemple), ou si cela ressemble à un hack : il existe probablement une meilleure solution. Un bon moyen de savoir si votre code est trop compliqué est la nécessité d’ajouter des commentaires pour l’expliquer : le code doit parler de lui-même.

De manière générale, les tests difficiles à écrire ou à comprendre sont le symptôme d’une mauvaise conception du code. Lorsque les tests d’un composant sont difficiles à écrire, vous pouvez extrapoler la difficulté d’utilisation de ce composant.

« S’il est difficile d’écrire un test, cela signifie que vous avez un problème de conception, pas un problème de test. Un code faiblement couplé et hautement cohésif est facile à tester. » (Extreme Programming Explained de Kent Beck).

Dans ce cas, vous devez déterminer si le code d’implémentation a besoin d’un refactoring pour faciliter le test et la maintenance. La solution peut être « diviser et conquérir », car votre SUT est peut-être un « god object ». Il peut également valoir la peine de se demander si vous effectuez un TDD avec des tests de trop haut niveau (par exemple, l’acceptation).

Découvrez les exemples de la TDD.

5. Optimiser la règle DRY (Do not Repeat Yourself)

Parmi les antipatrons c’est l’erreur la plus courante. Les gens voient une valeur répétée et ils créeront immédiatement une variable pour elle. Ce n’est pas une optimisation précoce : cela rend simplement la documentation (les tests) plus difficile à lire.

Dans les tests, certaines répétitions sont supportables par souci de clarté. C’est l’une des rares différences entre le code de test et le code d’implémentation. Le code de test est différent du code d’implémentation.

Sur une note connexe, faire défiler de haut en bas ou ouvrir plusieurs fichiers pour comprendre un seul test peut être frustrant. Un cas typique est un dossier rempli d’exemples de données qui est utilisé partout. Parfois, il est correct de laisser une configuration et un démontage répétés dans tous les tests dans tous les cas. Les tests doivent être évidents. Les tests sont de la documentation. Par conséquent, il est préférable de lire les tests de haut en bas, comme dans un manuel d’utilisation (imaginez si vous deviez changer de page plusieurs fois lors de la lecture du manuel de votre machine à laver…). C’est aussi une légende pour éviter d’utiliser des « globals » dans les tests (c’est-à-dire, des « globals » de classe ou de fichier).Globals

6. Des cas de tests très similaires

Le copier-coller de plus d’une ligne de code devrait généralement vous interloquer. Eh bien… vous pouvez le faire juste pour le faire fonctionner, mais vous devrez arranger ça plus tard. C’est encore pire si vous copiez un test entier où seules les valeurs initiales varient. Cela implique un recours au « table-driven testing », ce qui permet d’exécuter le même test plusieurs fois : une par ensemble d’entrées et de sorties attendue (ou d’autres conséquences).

Input output

L’itération manuelle des cas de test est un antipatron. Laissez cela au Framework de test.

Utilisez des « table-driven testing » si vous continuez à dupliquer le même test avec des données différentes. Les avantages sont les suivants :

  • Moins de code car vous ne répétez pas des tests très similaires – par conséquent, plus faciles à lire et à maintenir.
  • Comparaison facile des cas de test.
  • Possibilité d’ajouter, de modifier ou de supprimer rapidement des cas de tests.
  • Meilleure documentation, car les cas positifs, négatifs et extrêmes sont explicites.

Dans les tests basés sur les tables, ne mélangez pas le chemin heureux avec les scénarios d’erreur. Ceux-ci doivent être séparés pour des raisons de clarté.

Enfin, considérez le compromis que vous faites, étant donné que les tests deviennent moins DAMP lors de l’application de cette technique.

7. Tout tester

Selon la loi des rendements décroissants, il existe un point où le coût d’ajout de tests ne compense plus ses bénéfices.

Courbe benefice cout

En d’autres termes, toutes les possibilités n’ont pas besoin d’être testées :

  • Certaines sont trop rares mais à faible impact ;
  • D’autres apparaîtront spontanément lors de l’utilisation du logiciel (par exemple, « env. var. manquant »)
  • Beaucoup n’auront pas un grand impact s’ils échouent (par exemple, le style CSS, les animations).

Comment déterminer si un certain test en vaut la peine ? Il faut calculer approximativement son rapport avantages/coûts : est-ce que l’avantage / coût est supérieur ou égal à 1 ?

Dans le cadre du bénéfice, il faut inclure la prévention permanente contre le problème (utiliser les tests unitaires comme filet de sécurité).

Pour le coût, tenez compte de l’effort de simulation du scénario de test et des ressources nécessaires (lignes de code à maintenir, temps d’exécution).

Autres antipatrons

  • Utiliser le mot « and » pour décrire un test : cela indique que vous devez diviser le test en deux, chaque partie affirmant / vérifiant des choses différentes (voir l’exemple de code ci-dessus).
  • Tester de nombreuses combinaisons (combinatorial explosion) : c’est une odeur que vous avez plusieurs composants en un. La solution est de les reconnaître et de les séparer lentement.
  • Ne pas traiter le code de test à sa juste valeur : le code de test est votre filet de sécurité, alors prenez-en bien soin. La plupart des bonnes pratiques de code sont transversales à tout code source.
  • Ne pas rejeter le code d’un pic, en particulier lorsque vous faites du TDD : un pic est un outil d’apprentissage. Son code ne doit pas être conservé mais il doit évoluer.
  • Test des règles du métier via l’interface utilisateur : les règles du métier doivent être testées quelle que soit l’interface utilisateur utilisée. Bien entendu, les tests de bout en bout sont une exception.
  • Ne pas privilégier la quantité au détriment de la diversité : les cas de test doivent être différents d’une manière ou d’une autre. Il ne faut pas ajouter de cas pour des raisons de quantité. Il en va de même pour les scénarios de test : ils doivent être variés.
  • Se moquer de ce que vous ne possédez pas : évitez les fuites de bibliothèques dans votre logiciel (runtime ou externe); il existe des alternatives comme les « wrappers » et les « mock servers ».
  • Conditionnel dans les tests : il ne faut pas avoir des « if » dans les tests. C’est la preuve que vous avez besoin d’un autre scénario de test.
  • Surévaluer la couverture des tests : la couverture ne devrait pas être quelque chose que vous recherchez uniquement pour le plaisir. Vous pouvez être proche de 100% et avoir encore des tests approximatifs. Quoi qu’il en soit, avec la TDD, la couverture est presque un concept hors de propos.
  • Optimisation précoce : en TDD, réduisez toujours la « distance » entre un état rouge (maintenant) et un état vert (futur). Faites l’implémentation dans les grandes lignes, même si cela veut dire qu’il faut retourner une constante ou bien faire une implémentation laide ou lente. Faites-le fonctionner puis peaufinez le ensuite. La raison n’est pas seulement psychologique (voir un état vert libérant de la dopamine) mais il s’agit également d’apprendre plus tôt. Vous en apprenez, dès le début, plus sur le problème et la solution (par exemple la conception de code) si vous le faites simplement passer.

Conclusion sur ces antipatrons

On pourrait remarquer que beaucoup d’antipatrons dans les tests peuvent être attribués à un antipatron dans l’implémentation : « the god object » : avoir d’énormes fichiers de test, avoir de longs tests, un test difficile à écrire, entre autres sont des signes avant-coureurs. Nous vous conseillons :

  • Ecrire des logiciels dans une approche « top-down », de sorte que vous puissiez plus facilement comprendre les « composants ci-dessous » dérivant des « composants ci-dessus » (et vous implémentez uniquement ce qui est nécessaire);
  • Reconnaissez quand vous avez plusieurs raisons de changer un composant afin de pouvoir le diviser (principe de responsabilité unique).

En bout de ligne : si c’est compliqué, c’est que vous le faites mal. La solution idéale est généralement simple : vous avez juste besoin de persévérer et de recommencer jusqu’à ce que vous y parveniez.


Source: medium.com

Pour en savoir plus sur les frameworks d’automatisation des test téléchargez notre livre blanc !

Vous souhaitez plus d’informations sur ces antipatrons ? Vous avez un projet d’automatisation de test ?