« LeSS - Tests unitaires » : différence entre les versions
De Wiki Agile
m Ajout encadré |
|||
| (23 versions intermédiaires par 2 utilisateurs non affichées) | |||
| Ligne 6 : | Ligne 6 : | ||
---- | ---- | ||
Traducteur : Nicolas Mereaux<br /> | Traducteur : Nicolas Mereaux<br /> | ||
Relecteur : Fabrice Aimetti<br /> | |||
Date : 29/06/2022<br /> | Date : 29/06/2022<br /> | ||
---- | ---- | ||
| Ligne 47 : | Ligne 48 : | ||
Il est légitime de vous poser cette question. Pourquoi n’utilisons-nous tout simplement pas de manière rigoureuse des tests fonctionnels automatisés ou des tests systèmes automatisés pour protéger le programme ? | Il est légitime de vous poser cette question. Pourquoi n’utilisons-nous tout simplement pas de manière rigoureuse des tests fonctionnels automatisés ou des tests systèmes automatisés pour protéger le programme ? | ||
''' | '''Le coût total de possession''' — Le test unitaire se base sur le niveau d’abstraction du langage de programmation utilisé. Il s’agit juste d’un bout de code qui va tester un autre bout de code. Il n’a pas besoin de s’exécuter sur le même environnement que celui en production. Pour un langage compilé, il n’est même pas nécessaire d’utiliser le même compilateur que celui en production. Le coût de la création et l’exécution est très faible. S’il est conçu correctement, le coût de maintenance est aussi très faible. Vous pouvez ne pas avoir le même niveau de confiance pour un cas de test unitaire qui s’est exécuté avec succès que pour un cas de test fonctionnel. Vous pourriez avoir besoin de plusieurs cas de test unitaire pour avoir le même niveau de confiance. Mais le coût de tous ces petits cas de test unitaire restent malgré tout plus faible que celui de plusieurs cas de test fonctionnel. | ||
'' | Si le code source n'a bénéficié d'aucun test unitaire depuis les deux dernières années, il y aura un coût supplémentaire pour lui en appliquer un. Ce coût a deux origines principales : | ||
# Le coût d’application d’un ''framework'' de test sur le code du projet. C'est relativement plus facile pour des langages de programmation dynamique comme Python, Ruby ou Javascript. De manière générale, c’est aussi relativement trivial pour des projets Java ou C#. Cela peut être toutefois assez compliqué pour un projet C/C++. Que ce soit facile ou difficile, c’est un investissement à ne faire qu’une seule fois. | |||
# Le code source existant n’est pas testable car le code a été conçu au départ sans prendre en compte l’aspect testabilité. Appliquer des tests unitaires à ce type de code source implique souvent de devoir améliorer la conception existante. Mener cette amélioration implique non seulement une augmentation du coût concernant la création des tests, mais a potentiellement un autre coût qui est lié à l'introduction de nouvelles anomalies en changeant ladite conception. Par conséquent, l’introduction de tests unitaires pour un code source existant se doit d’être combinée avec d’autres types de travaux qui nécessiteront des modifications dans le code sous test — autrement dit lorsque le moment sera venu de changer ce morceau de code. | |||
'''Qualité interne vs. Qualité Externe''' — Les tests automatisés de haut niveau comme les tests fonctionnels et les tests systèmes contrôlent la qualité externe du logiciel. La qualité externe permet de connaître le niveau de fonctionnement du logiciel par rapport aux exigences. En effet, les tests unitaires ne sont pas aussi efficaces que les tests fonctionnels pour protéger la qualité externe. Par contre, les tests unitaires s’assurent de la qualité interne du logiciel. La qualité interne veut dire ici la testabilité du code et permet de savoir jusqu’à quel niveau le code est protégé. Une conception testable est synonyme en général de bonne conception. D’autres types de tests automatisés ne remplissent pas ce rôle aussi bien que les tests unitaires. | |||
''' | '''Qualité du ''feedback''''' — Après avoir passé un test fonctionnel, vous pouvez être confiant vis-à-vis de la fonctionnalité testée. Mais lorsque vous vous apercevez que le test échoue, vous devez généralement faire du déboggage pour trouver ce qui est erroné. Les tests unitaires peuvent vous donner une information plus précise sur ce qui fonctionne ou ne fonctionne pas. | ||
Les tests unitaires étant sur le même niveau d’abstraction que le langage de programmation employé, tout devrait s’exécuter au niveau du micro-processeur et de la mémoire lors de l’exécution du test unitaire. Et comme chaque cas de test devrait être de petite taille, il devrait donc s’exécuter très rapidement. Ainsi, vous devriez être en mesure d’exécuter plusieurs centaines de tests unitaires en quelques secondes. En prenant en compte le temps de compilation ou de préparation, l’ensemble du processus d’exécution d’un test unitaire devrait prendre moins d’une minute. | |||
Les tests unitaires devraient aussi être répétables. Si rien ne change d’une fois sur l’autre, l’exécution d’un test unitaire devrait toujours retourner le même résultat. | |||
Si un test unitaire est très rapide et répétable, les développeurs peuvent l’exécuter aussi souvent qu’ils le veulent, par exemple toutes les deux minutes. Le test unitaire fournira en permanence aux développeurs des retours d’informations liés à la qualité. Cela permettra ainsi aux développeurs d’avancer à un rythme régulier et de se focaliser sur des choses plus importantes plutôt que de passer trop d’énergie sur des choses triviales. | |||
Si un test unitaire est très rapide et répétable, les développeurs peuvent l’exécuter aussi souvent qu’ils le veulent, par exemple toutes les | |||
[[Image:Xtest_levels_fr.png|Xtest_levels_fr.png|border|link=|700px]] | [[Image:Xtest_levels_fr.png|Xtest_levels_fr.png|border|link=|700px]] | ||
Une structure de test automatisé acceptable devrait ressembler à une pyramide. À la base de la pyramide, il y a un grand nombre de cas de tests unitaires. Au milieu, des cas de tests d’intégration en moins grand nombre. En haut, seulement quelques cas de tests fonctionnels ou systèmes. | Une structure de test automatisé acceptable devrait ressembler à une pyramide. À la base de la pyramide, il y a un grand nombre de cas de tests unitaires. Au milieu, des cas de tests d’intégration en moins grand nombre. En haut, seulement quelques cas de tests fonctionnels ou systèmes. | ||
== Idées fausses à propos du test unitaire == | == Idées fausses à propos du test unitaire == | ||
=== Le test unitaire n’est pas aussi vital que le code en production === | === Le test unitaire n’est pas aussi vital que le code en production === | ||
Il est vrai qu’en fin de compte, c’est le code en production qui donne vraiment vie au produit. Mais la plupart des produits logiciels ont des cycles de vie évolutif. Le code n’est pas statique. Il change avec le temps. Un code sans test unitaire n’est pas suffisamment protégé lorsqu’une modification est faite. Le test unitaire contient aussi des informations importantes qui ne sont pas présentes dans le code en production. | |||
Il est vrai qu’en fin de compte, c’est le code en production qui donne vraiment vie au produit. Mais la plupart des produits logiciels ont des cycles de vie évolutif. Le code n’est pas statique. Il change avec le temps. | |||
Par conséquent le test unitaire est tout aussi important que le code en production. Il devrait être stocké '''dans le même dépôt de gestion du code source'''. Le test unitaire devrait d’ailleurs suivre les mêmes conventions de codage que le code en production. | Par conséquent le test unitaire est tout aussi important que le code en production. Il devrait être stocké '''dans le même dépôt de gestion du code source'''. Le test unitaire devrait d’ailleurs suivre les mêmes conventions de codage que le code en production. | ||
=== Le test unitaire est fait par des ingénieurs tests === | === Le test unitaire est fait par des ingénieurs tests === | ||
L’objectif du test unitaire n’est pas de trouver des anomalies. Techniquement, il ''vérifie'' plutôt que ''teste'' si le code sous test a implémenté le comportement voulu par le développeur qui l’a conçu. Donc le choix logique est de simplement laisser la même personne écrire à la fois le test et le code sous test. | L’objectif du test unitaire n’est pas de trouver des anomalies. Techniquement, il ''vérifie'' plutôt que ''teste'' si le code sous test a implémenté le comportement voulu par le développeur qui l’a conçu. Donc le choix logique est de simplement laisser la même personne écrire à la fois le test et le code sous test. | ||
Il est aussi encouragé d’avoir deux ou plusieurs personnes travaillant de concert pour programmer à la fois le test et le code sous test. Il existe plusieurs manières sympa pour programmer en binôme. Vous trouverez davantage d’informations à ce sujet dans la section développement piloté par les tests. | Il est aussi encouragé d’avoir deux ou plusieurs personnes travaillant de concert pour programmer à la fois le test et le code sous test. Il existe plusieurs manières sympa pour programmer en binôme. Vous trouverez davantage d’informations à ce sujet dans la section développement piloté par les tests. | ||
=== Vous pouvez écrire des tests unitaires sans changer le code sous test. === | === Vous pouvez écrire des tests unitaires sans changer le code sous test. === | ||
Ce n’est pas toujours le cas. Si le code n’a pas une bonne testabilité, vous pourriez tout de même être capable techniquement d’écrire le test unitaire qui va avec. Mais un test unitaire qui a été écrit pour un code non-testable est généralement très difficile à maintenir et à comprendre. Par conséquent, il n’y a pas vraiment de raison d’en avoir un. | Ce n’est pas toujours le cas. Si le code n’a pas une bonne testabilité, vous pourriez tout de même être capable techniquement d’écrire le test unitaire qui va avec. Mais un test unitaire qui a été écrit pour un code non-testable est généralement très difficile à maintenir et à comprendre. Par conséquent, il n’y a pas vraiment de raison d’en avoir un. | ||
'''Le secret du test unitaire n’est pas d’écrire du test, mais d’écrire un code testable sous test.''' Nous voulons du code testable et facile à tester, ce qui s’avère une démarche gagnant-gagnant. Nous ne voulons pas de code non-testable et difficile à maintenir, ce qui serait une démarche perdant-perdant. | |||
'''Le secret du test unitaire n’est pas d’écrire du test, mais d’écrire | |||
=== Je peux ajouter les tests unitaires plus tard === | === Je peux ajouter les tests unitaires plus tard === | ||
Eh bien, essayez donc de demander à des grimpeurs de mettre leurs pitons plus tard. | Eh bien, essayez donc de demander à des grimpeurs de mettre leurs pitons plus tard. | ||
[[Image:Xunit_test_fr.png|Xunit_test_fr.png|border|link=|500px]] | |||
[[Image:Xunit_test_fr.png|Xunit_test_fr.png|border|link=]] | |||
== Schéma pour de bons tests unitaires == | == Schéma pour de bons tests unitaires == | ||
=== Pas de nouvelles, bonnes nouvelles === | === Pas de nouvelles, bonnes nouvelles === | ||
Si le test passe, il devrait afficher seulement OK (voire quelques points pour afficher son avancement). Aucune autre information n'est nécessaire. | |||
Si le test passe, il devrait afficher seulement OK (voire quelques points pour afficher son avancement). Aucune autre information nécessaire. | |||
[[Image:unit_test_success.png|unit_test_success.png|border|link=]] | [[Image:unit_test_success.png|unit_test_success.png|border|link=|800px]] | ||
Règle empirique : | |||
Aucune intervention humaine ne devrait être nécessaire pour préparer l’exécution du test, exécuter les cas de tests ou en vérifier les résultats. | |||
Et lorsqu’un test unitaire échoue, il devrait nous fournir toutes les informations nécessaires. L’objectif est de limiter la durée pendant laquelle vous êtes occupé à débogguer le code concerné. | |||
[[Image:342xNxunit_test_fail.png|342xNxunit_test_fail.png|border|link=]] | [[Image:342xNxunit_test_fail.png|342xNxunit_test_fail.png|border|link=]] | ||
=== Arranger, Agir, Auditer (''Arrange'', ''Act'', ''Assert'') === | === Arranger, Agir, Auditer (''Arrange'', ''Act'', ''Assert'') === | ||
Un bon schéma à suivre en ce qui concerne les tests unitaires est "'''AAA'''" : '''Arrange (dans le sens de mettre en place)''', '''Act (dans le sens d’une action faite sur quelque chose)''' et '''Assert (dans le sens de contrôler)'''. | |||
Un bon schéma à suivre en ce qui concerne les tests unitaires est | |||
Si vous pouvez repérer ce schéma dans chacun de vos cas de tests, vos tests devraient facile à comprendre, et ils devraient s’avérer suffisamment spécifiques et aller droit au but. Un cas de test unitaire devrait tester une seule et unique chose. Par conséquent, il devrait y avoir un seul AAA dans un cas de test. Un cas de test ne devrait pas être très prolixe (c’est-à-dire plus de 10 lignes de code) s’il suit le schéma AAA. | Si vous pouvez repérer ce schéma dans chacun de vos cas de tests, vos tests devraient être facile à comprendre, et ils devraient s’avérer suffisamment spécifiques et aller droit au but. Un cas de test unitaire devrait tester une seule et unique chose. Par conséquent, il devrait y avoir un seul AAA dans un cas de test. Un cas de test ne devrait pas être très prolixe (c’est-à-dire plus de 10 lignes de code) s’il suit le schéma AAA. | ||
<pre>import unittest class TestGroupForTextWrapping(unittest.TestCase): | <pre>import unittest class TestGroupForTextWrapping(unittest.TestCase): | ||
| Ligne 192 : | Ligne 132 : | ||
=== Développement piloté par le comportement (BDD) === | === Développement piloté par le comportement (BDD) === | ||
Identique au schéma '''AAA''', le '''BDD''' utilise trois mots-clés différents pour spécifier chaque cas de test : '''Étant donné''', '''Lorsque''' et '''Alors'''. (Vous pouvez aussi utiliser '''Et''' comme mot-clé supplémentaire) | |||
<pre> | |||
Given / Étant donné que la longueur du texte pour le retour à la ligne est défini à 10 | Given / Étant donné que la longueur du texte pour le retour à la ligne est défini à 10 | ||
And / Et que le caractère '-' est utilisé comme connecteur entre deux mots | And / Et que le caractère '-' est utilisé comme connecteur entre deux mots | ||
When / Lorsque la longueur du texte est inférieure à 10 | When / Lorsque la longueur du texte est inférieure à 10 | ||
Then / Alors le texte ne devrait pas être retourné à la ligne</pre> | Then / Alors le texte ne devrait pas être retourné à la ligne</pre> | ||
Comme vous pouvez le constater, le triptyque "étant donné - lorsque - alors" s'allie plutôt bien avec le triptyque "Arrange - Act - Assert". Ils définissent tous les deux un état transition d’une machine à état finie. Vous pouvez en savoir plus en consultant cet article d’[https://sites.google.com/site/unclebobconsultingllc/the-truth-about-bdd Oncle Bob]. Voici quelques différences entre les deux : | |||
* Le BDD est davantage orienté "dehors vers dedans", cela veut dire qu’il met davantage l’accent sur le comportement externe | |||
* Le BDD est davantage | * Avec le BDD, vous devez définir un '''langage spécifique au domaine''' pour écrire vos spécifications de tests. À cause de cela, vous aurez besoin généralement d’un ''framework'' supplémentaire. Par exemple, pour Python vous pourrez utilisez [https://pypi.org/project/behave/ behave]. | ||
* Avec le BDD, vous devez définir un '''langage spécifique au domaine''' pour écrire vos spécifications de tests. À cause de cela, vous aurez besoin généralement d’un ''framework'' supplémentaire. Par exemple, pour Python vous pourrez utilisez [ | |||
=== La règle d’or du test unitaire === | === La règle d’or du test unitaire === | ||
De manière générale, une bonne règle d’or pour des tests unitaires serait : | De manière générale, une bonne règle d’or pour des tests unitaires serait : | ||
'''Chaque cas de test unitaire devrait comporter un périmètre très restreint.''' | |||
De telle manière que : | De telle manière que... : | ||
* Lorsque le test échoue, qu’il ne soit pas nécessaire de faire du déboggage pour localiser le problème. | * Lorsque le test échoue, qu’il ne soit pas nécessaire de faire du déboggage pour localiser le problème. | ||
* Les tests soient stables car les dépendances sont limitées. | * Les tests soient stables car les dépendances sont limitées. | ||
* Il y ait moins de duplications, que ce soit plus facile à maintenir | * Il y ait moins de duplications, que ce soit plus facile à maintenir. | ||
Il n’existe pas de secrets pour écrire un bon test unitaire. Afin d’écrire un bon test unitaire, vous devez créer une conception qui soit facile à tester. | Il n’existe pas de secrets pour écrire un bon test unitaire. Afin d’écrire un bon test unitaire, vous devez créer une conception qui soit facile à tester. | ||