Simplifiez les flux de travail de conformité avec le nouveau test C/C++ 2024.2 et l'automatisation pilotée par l'IA | Inscrivez-vous
Aller à la section
Bonnes pratiques de test unitaire: comment tirer le meilleur parti de votre automatisation de test
Les tests unitaires aident les organisations à tester leurs charges de travail unité par unité, mais la question est de savoir quelles sont les meilleures façons d'aborder les tests unitaires ? Consultez cet article pour découvrir les meilleures pratiques en matière de tests unitaires.
Aller à la section
Aller à la section
Les tests unitaires sont une pratique bien connue, mais il y a beaucoup de place à l'amélioration ! Ce poste couvre les meilleures pratiques de tests unitaires les plus efficaces, y compris des approches pour optimiser vos outils d'automatisation en cours de route. Nous discuterons également de la couverture du code, des dépendances fictives et des stratégies de test globales.
Qu'est-ce que le test unitaire?
Tests unitaires est la pratique consistant à tester des unités ou des composants individuels d'une application, afin de valider que chacune de ces unités fonctionne correctement. Généralement, une unité doit être une petite partie de l'application ; en Java, il s'agit souvent d'une seule classe. Notez que je ne définis pas ici strictement « unité », et c'est au développeur de décider de la portée du code testé pour chaque test.
Les gens comparent parfois le terme «test unitaire» avec «test d'intégration» ou «test de bout en bout». La différence est qu'en général, les tests unitaires sont effectués pour valider le comportement d'une unité testable individuelle, tandis que les tests d'intégration valident le comportement de plusieurs composants ensemble ou de l'application dans son ensemble. Comme je l'ai dit, la définition de ce qui constitue une «unité» n'est pas strictement définie, et c'est à vous de décider de la portée de chaque test.
Pourquoi un test unitaire?
Les tests unitaires sont une technique éprouvée pour garantir la qualité des logiciels, avec de nombreux avantages. Voici (plus de) quelques bonnes raisons de faire un test unitaire:
- Tests unitaires valide que chaque élément de votre logiciel fonctionne non seulement correctement aujourd'hui, mais continue de fonctionner à l'avenir, fournissant une base solide pour le développement futur.
- Tests unitaires identifie les défauts à un stade précoce du processus de production, qui réduit les coûts de les fixer à des stades ultérieurs du cycle de développement.
- Le code testé à l'unité est généralement plus sûr à refactoriser, puisque les tests peuvent être réexécutés rapidement pour valider que le comportement n'a pas changé.
- L'écriture de tests unitaires oblige les développeurs à considérer dans quelle mesure le code de production est conçu pour le rendre adapté aux tests unitaires, et incite les développeurs à regarder leur code à partir d'un autre point de vue, les encourageant à prendre en compte les cas de coin et les conditions d'erreur dans leur mise en œuvre.
- Inclure les tests unitaires dans le processus de révision du code peut révéler comment le code modifié ou nouveau est censé fonctionner. De plus, les examinateurs peuvent confirmer si les tests sont bons ou non.
Il est malheureux que trop souvent les développeurs n'écrivent pas du tout de tests unitaires, n'écrivent pas assez de tests ou ne les maintiennent pas. Je comprends - les tests unitaires peuvent parfois être difficiles à rédiger ou prendre du temps à maintenir. Parfois, il y a une date limite à respecter, et on a l'impression que la rédaction de tests nous fera manquer cette date limite. Mais ne pas écrire suffisamment de tests unitaires ou ne pas écrire de bons tests unitaires est un piège risqué dans lequel tomber.
Veuillez donc tenir compte de mes recommandations de bonnes pratiques suivantes sur la façon d'écrire des tests propres, maintenables et automatisés qui vous offrent tous les avantages des tests unitaires, avec un minimum de temps et d'efforts.
Bonnes pratiques de test unitaire
Examinons quelques bonnes pratiques pour créer, exécuter et gérer des tests unitaires, afin d'obtenir les meilleurs résultats.
Les tests unitaires doivent être dignes de confiance
Le test doit échouer si le code est cassé et uniquement si le code est cassé. Si ce n'est pas le cas, nous ne pouvons pas faire confiance à ce que les résultats des tests nous disent.
Les tests unitaires doivent être maintenables et lisibles
Lorsque le code de production change, les tests doivent souvent être mis à jour et éventuellement débogués. Il doit donc être facile de lire et de comprendre le test, non seulement pour celui qui l'a écrit, mais aussi pour les autres développeurs. Organisez et nommez toujours vos tests pour plus de clarté et de lisibilité.
Les tests unitaires doivent vérifier un cas d'utilisation unique
Les bons tests valident une chose et une seule chose, ce qui signifie qu'en général, ils valident un seul cas d'utilisation. Les tests qui suivent cette meilleure pratique sont plus simples et plus compréhensibles, ce qui est bon pour la maintenabilité et le débogage. Les tests qui valident plus d'une chose peuvent facilement devenir complexes et prendre du temps à maintenir. Ne laissez pas cela arriver.
Une autre bonne pratique consiste à utiliser un nombre minimal d'assertions. Certaines personnes recommandent une seule assertion par test (cela peut être un peu trop restrictif); l'idée est de se concentrer sur la validation uniquement de ce qui est nécessaire pour le cas d'utilisation que vous testez.
Les tests unitaires doivent être isolés
Les tests doivent être exécutables sur n'importe quelle machine, dans n'importe quel ordre, sans affecter les uns les autres. Si possible, les tests ne doivent pas dépendre des facteurs environnementaux ou de l'état global / externe. Les tests qui ont ces dépendances sont plus difficiles à exécuter et généralement instables, ce qui les rend plus difficiles à déboguer et à corriger, et finit par coûter plus de temps qu'ils n'en économisent (voir digne de confiance, au dessus).
Martin Fowler, il y a quelques années, a écrit sur le code «solitaire» ou «sociable», pour décrire l'utilisation des dépendances dans le code d'application et comment les tests doivent être conçus en conséquence. Dans son article, le code «solitaire» ne dépend pas d'autres unités (il est plus autonome), alors que le code «sociable» interagit avec d'autres composants. Si le code de l'application est solitaire, alors le test est simple… mais pour le code sociable en cours de test, vous pouvez soit construire un test «solitaire» ou «sociable». Un «test sociable» s'appuierait sur des dépendances réelles pour valider le comportement, alors qu'un «test solitaire» isole le code testé des dépendances. Vous pouvez utiliser des simulations pour isoler le code à tester et créer un test «solitaire» pour le code «sociable». Nous verrons comment procéder ci-dessous.
En général, l'utilisation de simulacres pour les dépendances nous facilite la vie en tant que testeurs, car nous pouvons générer des «tests solitaires» pour du code sociable. Un test sociable pour un code complexe peut nécessiter beaucoup de configuration et enfreindre les principes d'isolement et de répétabilité. Mais comme la maquette est créée et configurée dans le test, elle est autonome et nous avons plus de contrôle sur le comportement des dépendances. De plus, nous pouvons tester plus de chemins de code. Par exemple, je peux renvoyer des valeurs personnalisées ou lever des exceptions du simulacre, afin de couvrir les conditions limites ou d'erreur.
Les tests unitaires doivent être automatisés
Assurez-vous de exécuter des tests unitaires dans un processus automatisé. Cela peut être quotidien, ou toutes les heures, ou dans le cadre d'un processus d'intégration ou de livraison continue. Les rapports doivent être accessibles et examinés par tous les membres de l'équipe. En équipe, discutez des métriques qui vous intéressent : couverture du code, couverture du code modifié, nombre de tests en cours d'exécution, performances, etc.
Il y a beaucoup à apprendre en regardant ces chiffres, et un grand changement dans ces chiffres indique souvent des régressions qui peuvent être corrigées immédiatement.
Ebook: Améliorez les tests unitaires pour Java avec l'automatisation
Utilisez un bon mélange de tests unitaires et d'intégration
Le livre de Michael Cohn, Réussir avec Agile: développement de logiciels avec Scrum, résout ce problème en utilisant un modèle de pyramide de test (voir l'illustration dans l'image ci-dessous). Il s'agit d'un modèle couramment utilisé pour décrire la répartition idéale des ressources de test. L'idée est qu'au fur et à mesure que vous montez dans la pyramide, les tests sont généralement plus complexes à construire, plus fragiles, plus lents à exécuter et plus lents à déboguer. Les niveaux inférieurs sont plus isolés et plus intégrés, plus rapides et plus simples à créer et à déboguer. Par conséquent, les tests unitaires automatisés devraient constituer la majeure partie de vos tests.
Les tests unitaires doivent valider tous les détails, les cas d'angle et les conditions aux limites, etc. Les tests de composants, d'intégration, d'interface utilisateur et fonctionnels doivent être utilisés avec plus de parcimonie, pour valider le comportement des API ou de l'application dans son ensemble. Les tests manuels doivent représenter un pourcentage minimal de la structure pyramidale globale, mais restent utiles pour l'acceptation de la version et les tests exploratoires. Ce modèle offre aux organisations un niveau élevé d'automatisation et de couverture des tests, afin qu'elles puissent intensifier leurs efforts de test et réduire au minimum les coûts associés à la création, à l'exécution et à la maintenance des tests.
Les tests unitaires doivent être exécutés dans le cadre d'une pratique de test organisée
Afin de garantir le succès de vos tests à tous les niveaux et de rendre le processus de test unitaire évolutif et durable, vous aurez besoin de pratiques supplémentaires en place. Tout d'abord, cela signifie écrire des tests unitaires au fur et à mesure que vous écrivez le code de votre application. Certaines organisations écrivent les tests avant le code de l'application (axé sur les tests or axé sur le comportement programmation). L'important est que les tests vont de pair avec le code de l'application. Les tests et le code de l'application devraient même être examinés ensemble dans le processus de révision du code. Les revues vous aident à comprendre le code en cours d'écriture (car elles peuvent voir le comportement attendu) et à améliorer les tests aussi!
L'écriture de tests avec du code ne concerne pas uniquement les nouveaux comportements ou les changements planifiés, elle est également essentielle pour les corrections de bogues. Chaque bogue que vous corrigez devrait avoir un test qui vérifie que le bogue est corrigé. Cela garantit que le bogue restera corrigé à l'avenir.
Adoptez une politique de tolérance zéro en cas d'échec des tests. Si votre équipe ignore les résultats des tests, pourquoi avoir des tests? Les échecs de test doivent indiquer de vrais problèmes… alors résolvez ces problèmes tout de suite, avant qu'ils ne gaspillent le temps du contrôle qualité, ou pire, qu'ils ne pénètrent dans le produit commercialisé.
Plus il faut de temps pour résoudre les échecs, plus ces échecs coûteront à votre organisation du temps et de l'argent. Donc, exécutez des tests pendant la refactorisation, exécutez des tests juste avant de valider le code, et ne laissez pas une tâche être considérée comme «terminée» tant que les tests ne réussissent pas.
Enfin, maintenir ces tests. Comme je l'ai déjà dit, si vous ne gardez pas ces tests à jour lorsque l'application change, ils perdent leur valeur. Surtout s'ils échouent, les tests échoués coûtent du temps et de l'argent à enquêter chaque fois qu'ils échouent. Refactorisez les tests selon vos besoins, lorsque le code change.
Comme vous pouvez le voir, maximiser vos retours sur argent et temps investi dans vos tests unitaires nécessite un certain investissement dans l'application des meilleures pratiques. Mais au final, les récompenses valent l'investissement initial.
Qu'en est-il de la couverture du code?
En général, la couverture du code est une mesure de la quantité de code de production exécutée pendant que votre tests automatisés sont en train de courir. En exécutant une suite de tests et en examinant les données de couverture de code, vous pouvez avoir une idée générale de la part de votre application qui est testée.
Il existe de nombreux types de couverture de code - les plus courants sont la couverture de ligne et la couverture de succursale. La plupart des outils se concentrent sur la couverture de ligne, qui vous indique simplement si une ligne spécifique a été couverte. Branch est plus granulaire, car il vous indique si chaque chemin à travers le code est couvert.
La couverture du code est une métrique importante, mais rappelez-vous que l'augmenter est un moyen d'arriver à une fin. C'est génial pour trouver des lacunes dans les tests, mais ce n'est pas la seule chose sur laquelle se concentrer. Veillez à ne pas dépenser trop d'efforts pour essayer d'atteindre une couverture à 100% - cela peut même ne pas être possible ou faisable, et vraiment la qualité de vos tests est la chose importante. Cela étant dit, atteindre au moins 60% de couverture pour vos projets est un bon point de départ, et 80% ou plus est un bon objectif à fixer. Évidemment, c'est à vous de décider quel devrait être cet objectif.
Il est également utile si vous disposez d'outils automatisés qui mesurent non seulement la couverture du code, mais également la quantité de code modifié couvert par les tests, car cela peut permettre de savoir si suffisamment de tests sont en cours d'écriture avec les modifications du code de production.
Voir ici un exemple de rapport de couverture de code du hub de reporting et d'analyse de Parasoft, que vous pouvez parcourir si vous utilisez Jtest Parasoft pour votre tests unitaires:
Une autre chose à garder à l'esprit est que, lors de l'écriture de nouveaux tests, veillez à ne vous concentrer que sur la couverture de ligne, car des lignes de code uniques peuvent entraîner plusieurs chemins de code, alors assurez-vous que vos tests valident ces chemins de code. La couverture de ligne est un indicateur rapide utile, mais ce n'est pas la seule chose à rechercher.
Le moyen le plus évident d'augmenter la couverture est simplement d'ajouter plus de tests pour plus de chemins de code et plus de cas d'utilisation de la méthode testée. Un moyen efficace d'augmenter la couverture consiste à utiliser des tests paramétrés. Pour Junit4, il y avait la fonctionnalité paramétrée Junit4 intégrée et des bibliothèques tierces comme JUnitParams. JUnit3 a un paramétrage intégré.
Enfin, si vous ne suivez pas déjà la couverture des tests, je vous recommande vivement de commencer. Il existe de nombreux outils qui peuvent vous aider, comme Jtest Parasoft. Commencez par mesurer vos chiffres de couverture actuels, puis fixez-vous des objectifs où elle devrait être, comblez d'abord les lacunes importantes, puis travaillez à partir de là.
Résumé
Bien que les tests unitaires soient une technique éprouvée pour garantir la qualité des logiciels, ils sont toujours considérés comme un fardeau pour les développeurs et de nombreuses équipes ont encore du mal avec cela. Afin de tirer le meilleur parti des tests et des outils de test automatisés, les tests doivent être fiables, maintenables, lisibles, autonomes et utilisés pour vérifier un cas d'utilisation unique. L'automatisation est essentielle pour rendre les tests unitaires réalisables et évolutifs.
En outre, les équipes logicielles doivent mettre en pratique de bonnes techniques de test, telles que la rédaction et la révision des tests parallèlement au code d'application, la maintenance des tests et la garantie que les tests échoués sont suivis et corrigés immédiatement. L'adoption de ces meilleures pratiques de test unitaire peut rapidement améliorer les résultats de vos tests unitaires.