test logiciel par laquelle le code source isolé est testé pour valider le comportement attendu.
Les tests unitaires décrivent les tests exécutés au niveau unitaire, par opposition aux tests d'intégration ou au niveau système .
SAGE . Ce projet proposait une approche basée sur les spécifications, où la phase de codage était suivie de « tests paramétriques » pour valider les sous-programmes composants par rapport à leurs spécifications, puis de « tests d'assemblage » pour les parties assemblées.En 1964, une approche similaire est décrite pour le logiciel du projet Mercury , où les unités individuelles développées par différents programmeurs étaient soumises à des tests unitaires avant d'être intégrées. En 1969, les méthodologies de test apparaissent plus structurées, avec des tests unitaires, des tests de composants et des tests d'intégration validant collectivement les parties individuelles écrites séparément et leur assemblage progressif en blocs plus importants. Certaines normes publiques adoptées à la fin des années 1960, telles que MIL-STD-483 et MIL-STD-490, ont contribué à une large acceptation des tests unitaires dans les grands projets.
À cette époque, les tests unitaires étaient interactifs ou automatisés , utilisant soit des tests codés, soit des outils de capture et de relecture. En 1989, Kent Beck a décrit un framework de test pour Smalltalk (appelé plus tard SUnit ) dans « Simple Smalltalk Testing: With Patterns ». En 1997, Kent Beck et Erich Gamma ont développé et publié JUnit , un framework de tests unitaires qui a rapidement gagné en popularité auprès des développeurs Java . Google a adopté les tests automatisés vers 2005-2006
Unité
Une unité est définie comme un comportement unique manifesté par le système testé (SUT), correspondant généralement à une exigence programmation procédurale ) ou à une méthode ou une classe (en programmation orientée objet ), les fonctions/méthodes et les modules/classes ne correspondent pas nécessairement à des unités. Du point de vue des exigences système, seul le périmètre du système est pertinent ; par conséquent, seuls les points d'entrée des comportements du système visibles de l'extérieur définissent des unités. automatisée . Les tests automatisés présentent des avantages tels que : une exécution fréquente des tests, l’absence de coûts de personnel et des tests cohérents et reproductibles.
Les tests sont souvent effectués par le programmeur qui écrit et modifie le code testé. Les tests unitaires peuvent être considérés comme faisant partie intégrante du processus de développement.
Critères de test
Pour cela, l'approche la plus couramment utilisée est la suivante : test - fonction - valeur attendue.
cas de test
Double test
Test paramétré
Un test paramétré est un test qui accepte un ensemble de valeurs permettant son exécution avec plusieurs valeurs d'entrée différentes. Un framework de test prenant en charge les tests paramétrés offre une méthode pour encoder les ensembles de paramètres et exécuter le test avec chaque ensemble.
L'utilisation de tests paramétrés permet de réduire la duplication du code de test.
Les tests paramétrés sont pris en charge par TestNG , JUnit , XUnit et NUnit , ainsi que dans divers frameworks de test JavaScript.le masquage des informations , l'encapsulation et la séparation des responsabilités . Pour permettre l'accès au code non exposé dans l'API externe, les tests unitaires peuvent être placés dans le même projet ou module que le code testé.
En conception orientée objet, cela peut ne pas permettre l'accès aux données et méthodes privées. Par conséquent, des efforts supplémentaires peuvent être nécessaires pour les tests unitaires. En Java et dans d'autres langages, un développeur peut utiliser la réflexion pour accéder aux champs et méthodes privés . Une autre solution consiste à utiliser une classe interne pour contenir les tests unitaires, afin qu'ils aient accès aux membres et attributs de la classe englobante. Dans le framework .NET et certains autres langages de programmation, les classes partielles permettent d'exposer les méthodes et données privées pour que les tests puissent y accéder.
Il est important que le code dédié aux tests ne soit pas intégré au code de production. En C et dans d'autres langages, des directives de compilation telles que ` #if DEBUG ... #endifandroid::test` peuvent être placées autour de ces classes supplémentaires, ainsi que de tout autre code lié aux tests, afin d'empêcher leur compilation dans le code final. Ainsi, le code final diffère légèrement du code testé unitairement. L'exécution régulière de tests d'intégration de bout en bout, moins nombreux mais plus complets, sur la version finale permet notamment de garantir qu'aucun code de production ne dépende, même indirectement, du cadre de test .
Il existe un débat parmi les développeurs quant à la pertinence de tester les méthodes et données privées. Certains estiment que les membres privés ne sont qu'un détail d'implémentation susceptible d'évoluer, et que cela devrait être autorisé sans impacter le nombre de tests. Il suffirait donc, selon eux, de tester une classe via son interface publique ou celle de sa sous-classe, parfois appelée interface « protégée ». D'autres affirment que des aspects essentiels des fonctionnalités peuvent être implémentés dans des méthodes privées et que les tester directement permet de réaliser des tests unitaires plus courts et plus ciblés.
Développement piloté par les tests
En écrivant d'abord des tests pour les plus petites unités testables, puis pour les comportements composés entre celles-ci, on peut construire des tests complets pour des applications complexes.
L'un des objectifs des tests unitaires est d'isoler chaque partie du programme et de démontrer que chaque partie est correcte. Un test unitaire fournit un contrat strict et écrit que le morceau de code doit respecter.
Détection précoce des problèmes dans le cycle de développement
Les tests unitaires permettent de détecter les problèmes dès les premières étapes du cycle de développement . Cela inclut les bogues dans l'implémentation du programmeur ainsi que les défauts ou les parties manquantes de la spécification de l'unité. Le processus de rédaction d'un ensemble complet de tests oblige l'auteur à réfléchir aux entrées, aux sorties et aux conditions d'erreur, et permet ainsi de définir plus précisément le comportement attendu de l'unité.
coût réduit
Le coût de la détection d'un bogue avant le début du développement ou lors de la première écriture du code est considérablement inférieur à celui de sa détection, de son identification et de sa correction ultérieures. Les bogues dans le code publié peuvent également engendrer des problèmes coûteux pour les utilisateurs finaux du logiciel. Un code mal écrit peut être impossible ou difficile à tester unitairement ; les tests unitaires incitent donc les développeurs à mieux structurer les fonctions et les objets.
Des sorties plus fréquentes
Les tests unitaires permettent des mises en production plus fréquentes dans le développement logiciel. En testant les composants individuellement, les développeurs peuvent identifier et résoudre rapidement les problèmes, ce qui accélère les cycles d'itération et de mise en production.
Permet la refactorisation du code
Les tests unitaires permettent au programmeur de remanier le code ou de mettre à jour les bibliothèques système ultérieurement, tout en s'assurant du bon fonctionnement du module (par exemple, lors de tests de régression ). La procédure consiste à rédiger des cas de test pour toutes les fonctions et méthodes afin de pouvoir identifier rapidement toute anomalie résultant d'une modification.
Détecte les modifications susceptibles de rompre un contrat de conception
Les tests unitaires détectent les modifications susceptibles de rompre un contrat de conception .
Réduire l'incertitude
Les tests unitaires permettent de réduire l'incertitude liée aux unités elles-mêmes et peuvent être utilisés selon une approche de test ascendante . En testant d'abord les parties d'un programme, puis l'ensemble de ces parties, les tests d'intégration sont grandement simplifiés.problème englobe le problème de l'arrêt , qui est indécidable . Il en va de même pour les tests unitaires. De plus, par définition, les tests unitaires ne testent que la fonctionnalité des unités elles-mêmes. Par conséquent, ils ne détectent pas les erreurs d'intégration ni les erreurs système plus générales (telles que les fonctions exécutées par plusieurs unités ou les aspects non fonctionnels comme les performances ). Les tests unitaires doivent être menés conjointement avec d'autres activités de test logiciel , car ils ne peuvent que révéler la présence ou l'absence d'erreurs particulières ; ils ne peuvent pas prouver l'absence totale d'erreurs. Pour garantir un comportement correct pour chaque chemin d'exécution et chaque entrée possible, et assurer l'absence d'erreurs, d'autres techniques sont nécessaires, notamment l'application de méthodes formelles pour prouver qu'un composant logiciel ne présente aucun comportement inattendu.l'intervention humaine ; les tests de haut niveau ou de portée globale étant difficiles à automatiser, les tests manuels apparaissent souvent plus rapides et moins coûteux.non déterministes ou qui impliquent plusieurs threads . De plus, le code d'un test unitaire est tout aussi susceptible de contenir des bogues que le code qu'il teste. Fred Brooks, dans son ouvrage « The Mythical Man-Month », écrit : « N'allez jamais en mer avec deux chronomètres ; prenez-en un ou trois. » (Si deux chronomètres donnent des résultats contradictoires, il est impossible de savoir lequel est correct.)
Difficulté à mettre en place des tests réalistes et utiles
Un autre défi lié à la rédaction des tests unitaires réside dans la difficulté de mettre en place des tests réalistes et utiles. Il est nécessaire de créer des conditions initiales pertinentes afin que la partie de l'application testée se comporte comme une partie du système complet. Si ces conditions initiales ne sont pas correctement définies, le test n'exécutera pas le code dans un contexte réaliste, ce qui diminue la valeur et la précision des résultats des tests unitaires.
Exige de la discipline tout au long du processus de développement
Pour tirer pleinement profit des tests unitaires, une discipline rigoureuse est nécessaire tout au long du processus de développement logiciel.
Nécessite un système de contrôle de version
Il est essentiel de consigner avec précision non seulement les tests effectués, mais aussi toutes les modifications apportées au code source de cette unité ou de toute autre unité du logiciel. L'utilisation d'un système de gestion de versions est indispensable. Si une version ultérieure de l'unité échoue à un test qu'elle réussissait auparavant, le logiciel de gestion de versions peut fournir la liste des modifications apportées au code source (le cas échéant) depuis lors.assert pour vérifier le résultat attendu des différentes valeurs d'entrée de la UML , mais des diagrammes peuvent être générés à partir du test unitaire à l'aide d'outils automatisés. La plupart des langages modernes disposent d'outils gratuits (généralement disponibles sous forme d'extensions pour les EDI ). Les outils gratuits, comme ceux basés sur le framework xUnit , externalisent le rendu graphique de l'interface utilisateur vers un autre système.
Applications
Programmation extrême
Les tests unitaires sont la pierre angulaire de la programmation extrême (XP) , qui repose sur un framework de tests unitaires automatisés . Ce framework peut être un framework tiers, comme xUnit , ou être créé en interne par l'équipe de développement.
La programmation extrême (XP) utilise la création de tests unitaires pour le développement piloté par les tests (TDD) . Le développeur écrit un test unitaire qui met en évidence soit une exigence logicielle, soit un défaut. Ce test échouera soit parce que l'exigence n'est pas encore implémentée, soit parce qu'il expose intentionnellement un défaut dans le code existant. Ensuite, le développeur écrit le code le plus simple possible pour que ce test, ainsi que les autres tests, réussissent.
La majeure partie du code d'un système est testée unitairement, mais pas nécessairement tous les chemins d'exécution. La programmation extrême préconise une stratégie de « test de tout ce qui peut potentiellement dysfonctionner », contrairement à la méthode traditionnelle de « test de chaque chemin d'exécution ». Cela conduit les développeurs à créer moins de tests qu'avec les méthodes classiques, mais ce n'est pas un problème en soi ; c'est plutôt un constat, car les méthodes classiques ont rarement été appliquées avec suffisamment de rigueur pour permettre un test exhaustif de tous les chemins d'exécution. La programmation extrême part du principe que les tests sont rarement exhaustifs (car ils sont souvent trop coûteux et trop longs pour être économiquement viables) et fournit des indications sur la manière d'optimiser l'utilisation des ressources limitées.
Point essentiel, le code de test est considéré comme un élément à part entière du projet : il est maintenu avec le même niveau de qualité que le code d'implémentation, et toute duplication est éliminée. Les développeurs publient le code des tests unitaires dans le dépôt de code en même temps que le code qu'il teste. Les tests unitaires rigoureux de la programmation extrême offrent les avantages mentionnés précédemment, tels qu'un développement et une refactorisation du code plus simples et plus fiables , une intégration simplifiée, une documentation précise et des conceptions plus modulaires. Ces tests unitaires sont également exécutés en continu comme test de régression .
Les tests unitaires sont également essentiels au concept de conception émergente , qui développe la conception logicielle de manière itérative grâce à de courts cycles de développement pilotés par les tests consistant à écrire un test unitaire, à le faire réussir et à refactoriser ; les tests unitaires fournissent le filet de sécurité contre la régression qui rend la refactorisation continue sûre.
Cadres de tests automatisés
Un framework de tests automatisés offre des fonctionnalités permettant d'automatiser l'exécution des tests et peut accélérer leur écriture et leur exécution. Des frameworks ont été développés pour une grande variété de langages de programmation .
En général, les frameworks sont des solutions tierces , non distribuées avec un compilateur ou un environnement de développement intégré (IDE).
Il est possible d'écrire des tests sans utiliser de framework pour exécuter le code à tester, en utilisant des assertions , la gestion des exceptions et d'autres mécanismes de contrôle de flux afin de vérifier le comportement et de signaler les erreurs. Certains soulignent que tester sans framework est précieux car l'adoption d'un framework représente un obstacle , et que quelques tests valent mieux que rien ; toutefois, une fois un framework en place, l'ajout de tests peut s'avérer plus simple.
Dans certains frameworks, les fonctionnalités de test avancées sont absentes et doivent être codées manuellement.
Prise en charge des tests unitaires au niveau du langage
Certains langages de programmation prennent directement en charge les tests unitaires. Leur syntaxe permet de déclarer directement des tests unitaires sans importer de bibliothèque (qu'elle soit tierce ou standard). De plus, les conditions booléennes des tests unitaires peuvent être exprimées avec la même syntaxe que les expressions booléennes utilisées dans le code non lié aux tests unitaires, comme par exemple pour les instructions `if` Cobra