
Code-Garage #63 - F.I.R.S.T : Les bonnes pratiques pour écrire des tests unitaires
Durée: 8m49s
Date de sortie: 13/06/2023
Pour écrire des tests, il ne suffit pas de regarder une fonction et de l'appeler dans ses jeux de tests, il y a 5 bonnes pratiques à respecter pour que vos tests soient utiles, maintenables et automatisables !
Notes de l'épisode :
- Farnell (sponsor) : https://fr.farnell.com
Salut et bienvenue dans ce nouvel épisode du podcast de Code Garage, je m'appelle Nicolas
Brandin-Bernard, et aujourd'hui on va parler de tests unitaires et plus particulièrement
de la méthode First.
Mais juste avant de rentrer dans le vif du sujet, je voulais réellement remercier notre
premier sponsor sur le podcast et qui va nous suivre pour plusieurs épisodes, c'est
tout simplement la plateforme Farnel.
Un réseau mondial de connaissance et l'engagement de vous accompagner à toutes les étapes
de vos projets.
De la recherche à la conception, en passant par la maintenance, Farnel, une équipe de
confiance.
Pour en savoir plus, rendez-vous sur fr.farnel.com.
Alors dans cet épisode, je ne vais pas revenir sur le concept de tests unitaires, je ne vais
pas lister tous les différents types de tests, etc.
On va plutôt se concentrer sur la méthode de testing qu'on appelle First, qui est
simplement un ensemble de bonnes pratiques qui se cachent derrière un acronyme, et donc
chaque lettre cache tout simplement une bonne pratique qu'on va lister ensemble,
et des bonnes pratiques pour quoi pour écrire tout simplement des vrais bons tests unitaires
qui soient utiles, qui soient efficaces et qui va vraiment améliorer la qualité de
notre codebase et éviter les bugs.
Le premier, le F, c'est pour fast.
Évidemment, ça peut paraître logique, mais il faut quand même le dire.
Si jamais on a une suite de tests qui met trop longtemps à s'exécuter, on ne va pas avoir
envie de la lancer.
Et des tests qui ne sont jamais lancés, ils ne servent absolument à rien.
On va d'ailleurs chercher à automatiser ces tests.
L'objectif est de gagner du temps, pas en perdre.
Donc si jamais vous avez des tests qui mettent trop longtemps à s'exécuter, vous n'allez
pas vouloir les automatiser à chaque commit, par exemple.
Et donc si vous ne les automatisez pas, vous n'allez jamais les exécuter ou alors trop
peu souvent et donc ça ne va servir à rien.
Donc il faut faire attention que le temps d'exécution de vos tests soit le plus petit
possible et le réduire de manière drastique.
Alors évidemment, ça se fait assez naturellement quand on teste unitèrement puisqu'on teste
juste une logique.
Normalement, on ne teste pas d'appel serveur, on ne teste pas d'enregistrement basse de
données, etc.
Et donc ça devrait être rapide de toute façon.
Mais des fois, on a le choix entre plusieurs solutions et il faut prendre la solution,
évidemment, la plus efficace, la plus efficiente.
Le I, c'est pour isoler isolated.
Et donc évidemment, c'est le principe même des tests unitaires.
Mais ça n'achevera pas seulement de prendre une seule fonction ou une seule méthode et de
tester celle-ci.
Mais ça signifie aussi d'éviter les effets de bord quand on a des variables globales,
des choses comme ça, qu'on essaye de toute façon d'éviter en conception logicielle.
Mais on peut s'en trouver quand on teste du code.
Et bien, on va éviter les effets de bord et on va éviter également, comment dire,
si jamais on a une méthode qui s'appelle get something ou watch something, track something,
et bien on va éviter d'avoir de la modification de valeur dans ces fonctions-là,
tout simplement parce que le nom de la fonction ne correspond plus du tout à ce qu'elle fait.
Et ça va consister aussi à créer un effet de bord.
Si jamais votre code n'est pas suffisamment isolé pour pouvoir être testé de manière unitaire,
ça indique qu'il va y avoir un besoin de refactoriser et de séparer vraiment la logique
du reste du code.
Ça arrive très souvent dans les composants web, les composants visuels, etc.
On se dit, oh non, mais je mets un tout petit peu de logique, ça va parce que le composant est petit.
Bah non, en fait, votre logique, elle devrait même pas être là, elle devrait être complètement séparée,
ce qui vous permettra de la tester très simplement avec des tests unitaires.
Le R de first, toujours, c'est pour répétable, repeatable.
Donc ça, c'est que votre suite de tests, elle doit pouvoir être lancée plusieurs fois.
Ne doit laisser aucune trace de données résiduelles et ne pas entamer une prochaine exécution.
Notamment, je l'ai dit, quand on va automatiser ces tests-là,
eh bien, ils peuvent être lancés une fois, dix fois à la suite, peu importe.
Si jamais on vient tester une fonctionnalité, si jamais on vient faire de modifications dans le code
et qu'on va relancer, relancer, relancer nos tests pour voir que notre refactor n'a pas entaché la fonctionnalité,
eh bien, tout simplement, on doit pouvoir répéter ces tests.
Donc ne pas laisser de données résiduelles, c'est vraiment un très gros point très important dans les tests unitaires.
Le S, qu'elle l'a vendu en dernière lettre, c'est pour self-validating.
Ça veut dire que chaque test doit fournir une sortie bouleaine, qui est souvent issu d'une assertion, vrai ou faux.
Et ça doit déterminer automatiquement si le système en cours se comporte correctement.
Ce que ça signifie, c'est tout simplement qu'il ne doit pas y avoir d'interventions manuelles
ou d'interprétations d'un humain pour valider le test.
Donc ça, c'est très important puisque sinon, évidemment, vous ne pouvez plus du tout automatiser ces tests-là.
Donc il faut que chaque test doit pouvoir s'auto-valider, savoir si la sortie est correcte ou incorrecte.
Donc très souvent, évidemment, c'est juste du boulet entre ou false.
Des fois, on a aussi les bibliothèques d'assertion, on utilise beaucoup les exceptions.
Exception, ça revient à avoir une sortie fausse, mais on ne va pas avoir de sortie plus complexe que ça de notre test.
Attention, pas de la méthode qu'on teste, la méthode qu'on teste peut nous renvoyer tout ce qu'on veut,
mais il faut que le test en lui-même soit capable d'évaluer ce résultat.
Et donc pour la dernière lettre, on a un petit peu de mots, mais qui en réalité se chevochent un petit peu.
On a d'un côté timely, c'est le T, donc timely, ça veut dire opportun.
Et on a sinon thorough et thorough, ça veut dire complet.
Donc je vais vous faire les deux et après, de toute façon, c'est un petit peu pour moi un embriquement, on va dire, de ces deux termes-là.
Donc opportun, ça veut dire que chaque test doit être écrit rapidement.
Si vous mettez 25 minutes à écrire un test unitaire, on parle.
C'est que le test est mal écrit, alors que la logique de la fonction que vous testez est trop complexe.
Donc c'est pareil, ça peut induire un refactor, mais chaque test, on doit mettre quelques, une, deux, trois minutes, grand maximum à l'écrire.
En amont ou en parallèle du code, on doit écrire ces tests.
Donc évidemment, si jamais vous faites du TDD, vous écrivez le test avant le code,
mais si jamais vous écrivez et vous avez décidé d'écrire vos tests après, sur quoi je vais pas porter de jugement,
eh ben tout simplement, il faut que le test soit écrit le plus tôt possible, juste après la création de votre code.
Puisque normalement, on écrit un test en fonction des spécifications et pas du code.
Donc si jamais vous écrivez un test sur du code qu'à deux ans, alors évidemment, ça peut être un très bon moyen de créer des tests de non-régression,
puisque le code marche comme ça et il va continuer de marcher comme ça et c'est très bien.
Mais en réalité, nous, ce qu'on veut, c'est tester en fonction des spécifications,
puisque sinon, on peut passer à côté d'erreurs de logique dans notre code, de cas qu'on n'a pas été testés, ou de choses comme ça.
Et si jamais on écrit notre test simplement en fonction du code, et ben évidemment, on va pas pouvoir détecter ces problèmes-là.
La deuxième version du TDD, où je vous disais, donc c'est Thorough complet.
Donc là, c'est d'essayer de tester au maximum votre logiciel, en tout cas, tester pour sûr l'entierté du Happy Path.
Donc le Happy Path, c'est quand tout se passe bien, quand nos fonctions n'envoient pas d'exception,
voilà, que tout se passe dans le chemin normal d'un utilisateur, mais évidemment aussi un maximum de cas invalid,
du domaine invalid. Donc quand tout se passe pas bien, on doit quand même pouvoir gérer ces exceptions-là.
Maintenant, ça veut pas dire faire 100% de coverage de votre code.
Évidemment, si vous avez déjà lancé un test de coverage, vous pouvez voir que selon votre configuration,
des fois, on peut avoir des fichiers qui vont passer dans le coverage, qui n'ont rien à faire là,
parce qu'il n'y a pas d'intérêt de les tester, il n'y a pas vraiment de logique dedans.
Donc quand on parle de complétude ici, ça veut pas dire avoir 100% de coverage,
ça veut dire avoir tout ce qui doit être testé en termes de logique testé, pas simplement tester à l'aveugle
et faire que toutes les petites lignes, elles passent vertes. Non, il faut que tester de manière logique et réfléchie.
Voilà, j'espère que ça vous aura été utile. Je vous conseille vraiment d'essayer de garder en tête cette méthode first
pour rappel, donc c'est Fast, Isolated, Reputable, Cell Validating et Timely, donc opportun ou thorough, complet.
Ça, ça vous aidera à écrire de bons tests et qui seront viables dans le temps, à la fois quand vous les écrivez,
mais qui seront viables dans le temps. Donc je vous conseille vraiment de le garder en tête,
c'est une des meilleures pratiques de tests que vous fassiez du TDD ou du Test Last.
Moi, je vous donne rendez-vous la semaine prochaine pour un prochain épisode du podcast
ou directement sur code-garrage.fr pour retrouver tous nos articles, nos podcasts et toutes les formations qui sont disponibles.
Episode suivant:
Les infos glanées
Code-Garage
Découvrons ensemble des sujets passionnants autour du métier de dev et de la programmation en général !
Tags
Code-Garage #64 - Comprendre la souveraineté numérique