Tests et qualité logicielle

Fondamentaux du test logiciel : niveaux, techniques et vocabulaire qui divise

Débutant12 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

Tester est une activité de conception bien avant d'être une affaire d'outils. Ce guide expose ce que les tests peuvent et ne peuvent pas prouver, les niveaux par lesquels passe une modification, les techniques formelles qui ramènent un espace d'entrées infini à un ensemble fini de cas, et le vocabulaire commun qui évite que les discussions sur les défauts ne tournent à la querelle de mots.

Ce que vous allez apprendre

  • Expliquer ce que les tests peuvent démontrer et ce qu'ils ne pourront jamais prouver
  • Distinguer les niveaux unitaire, intégration, système et acceptation, et ce à quoi chacun sert le mieux
  • Appliquer les partitions d'équivalence, les valeurs limites, les tables de décision et les transitions d'état pour dériver des cas de test
  • Employer erreur, défaut et défaillance, ainsi que vérification et validation, dans leur sens précis
  • Évaluer la pyramide des tests sur le fond et reconnaître où ses détracteurs ont raison

Tout programme non trivial a des comportements que ses auteurs n'ont jamais envisagés. Les tests sont le moyen pour une équipe d'en découvrir une partie avant les clients et, surtout pour ceux qui maintiennent le code, de garder la liberté de le faire évoluer plus tard. Une suite qui échoue bruyamment quand un refactoring casse une règle vaut davantage qu'une suite qui prouve simplement que le code a fonctionné une fois, car la première continue de rapporter pendant des années.

Le volet outillage du test s'apprend vite et se surestime facilement. Savoir écrire une assertion JUnit ne dit rien sur quelles assertions valent la peine d'être écrites. Ce guide traite de ce qui vient avant : ce que les tests permettent d'établir, comment le travail se répartit entre les niveaux, comment dériver des cas méthodiquement plutôt qu'au jugé, et le petit vocabulaire qui permet à une équipe de discuter d'un défaut sans que trois personnes entendent trois choses différentes.

Ce qu'un test peut établir, et ce qu'il ne peut pas

Un test exécute un programme avec des entrées choisies dans un état choisi, puis compare le résultat observé à un résultat attendu. Comme l'espace des entrées d'un service même modeste est astronomique — toutes les chaînes, toutes les dates, tous les ordres possibles de requêtes concurrentes, toutes les pannes partielles d'une dépendance —, le test exhaustif n'existe tout simplement pas. Ce que vous construisez réellement est un échantillon, choisi pour maximiser les chances de révéler des défaillances par unité d'effort.

Il en découle deux conséquences pratiques. D'abord, une suite au vert n'est pas une preuve de correction : elle signifie que les défaillances que vous avez pensé à chercher ne se sont pas produites dans les conditions que vous avez créées. Ensuite, la valeur de l'échantillon dépend presque entièrement de la façon dont il a été choisi. Mille tests sur le même cas nominal valent moins que vingt tests qui sondent chacun une limite différente, une branche d'erreur ou une hypothèse sur un collaborateur.

C'est aussi pourquoi le moment où s'exécute un test compte autant que son contenu. Le coût d'un défaut croît avec l'écart entre le moment où il est introduit et celui où il est trouvé. Une erreur détectée par le compilateur coûte quelques secondes. Par un test unitaire sur le poste du développeur, quelques minutes. Dans un pipeline, une heure de changement de contexte. En production, elle coûte un incident, un hotfix, un plan de rollback, parfois une réparation de données, et l'attention de personnes qui faisaient autre chose. Les multiplicateurs exacts cités dans la littérature varient énormément et méritent la plus grande méfiance, mais l'allure de la courbe ne fait pas débat, et elle justifie de pousser la détection aussi tôt que possible.

Des mots qui doivent n'avoir qu'un sens

La plupart des discussions houleuses sur les tests portent en réalité sur des définitions. Quatre distinctions méritent d'être fixées.

Erreur, défaut, défaillance. Une personne commet une erreur (une faute de raisonnement). Cette erreur peut laisser un défaut dans un artefact : du code, un fichier de configuration, une exigence. Quand le code défectueux s'exécute dans des conditions qui le révèlent, on obtient une défaillance : un comportement observable qui diffère de ce qui était attendu. Tout défaut ne provoque pas de défaillance, et une défaillance peut aussi venir de l'environnement ou des données sans aucun défaut en cause. Cette précision fait gagner du temps lors des revues d'incident : « nous avons eu une défaillance » et « nous avons eu un défaut » appellent des investigations différentes.

Vérification et validation. La vérification confronte le produit à sa spécification. La validation confronte la spécification au besoin. Les tests automatisés relèvent dans leur immense majorité de la vérification ; c'est dans les démos, les sessions de recette et les tests d'utilisabilité que se fait la validation.

Test et assurance qualité. Le test mesure un produit. L'assurance qualité est l'ensemble plus large de pratiques — revues, normes, analyse statique, definition of done — qui rendent les défauts moins probables dès le départ.

Cas de test, suite de tests, exécution. Un cas est une spécification unique d'entrées, de préconditions et de résultat attendu. Une suite regroupe des cas. Une exécution est un passage d'une suite sur un build dans un environnement. Annoncer « le test a échoué » sans préciser quelle exécution, sur quel build, c'est ainsi que les équipes courent après des fantômes.

Les niveaux que traverse une modification

Les niveaux décrivent le périmètre de ce qui est testé, pas l'outil utilisé.

NiveauPérimètreGénéralement écrit parRépond à
Composant (unitaire)Une classe ou un petit groupe, isolé des collaborateurs lentsLes développeursCette logique se comporte-t-elle comme spécifié ?
IntégrationDeux composants ou plus, ou un composant et un vrai système externeLes développeursCes parties s'accordent-elles sur leur contrat ?
SystèmeL'application déployée dans son ensembleDéveloppeurs et testeursLe produit assemblé satisfait-il ses exigences ?
AcceptationLe produit dans son contexte métierLes équipes produit, les utilisateursEst-ce ce dont nous avions besoin, et pouvons-nous livrer ?

Les frontières entre niveaux sont des conventions, pas des lois, et les équipes débattent sans fin pour savoir si un test qui démarre une base de données est « unitaire » ou « d'intégration ». Le débat est généralement stérile ; ce qui compte, c'est que chaque test ait un sujet clairement identifié et qu'un échec pointe vers un endroit précis. Un test dont l'échec peut signifier neuf choses différentes est mal délimité, quel que soit le nom qu'on lui donne. Le guide des tests unitaires et le guide des tests d'intégration détaillent la façon de construire chacun en Java.

Les tests d'acceptation se déclinent en variantes qu'il vaut la peine de nommer : la recette utilisateur (UAT), menée par ceux qui utiliseront le système ; l'acceptation opérationnelle, qui couvre sauvegarde, restauration, supervision et runbooks ; l'acceptation contractuelle ou réglementaire, lorsqu'il faut démontrer la conformité à une norme externe ; et les tests alpha/bêta, où de vrais utilisateurs éprouvent une version préliminaire.

Le fonctionnel, et tout le reste

Les tests fonctionnels se demandent si le système fait ce qu'il faut. Les tests non fonctionnels se demandent à quel point il le fait bien, et c'est de là que viennent en réalité la plupart des incidents de production. Les principales familles :

  • Efficacité des performances — temps de réponse, débit, consommation de ressources sous charge. Traitée dans le guide des tests de performance.
  • Fiabilité — comportement quand une dépendance tombe, reprise après un crash, durabilité des données.
  • Sécurité — authentification, autorisation, injection, gestion des secrets, vulnérabilités des dépendances.
  • Utilisabilité et accessibilité — une personne peut-elle mener la tâche à bien, y compris avec un lecteur d'écran ou uniquement au clavier.
  • Compatibilité — navigateurs, appareils, versions de base de données, anciens clients d'API.
  • Maintenabilité et portabilité — des qualités mesurées sur le code et le déploiement plutôt qu'à l'exécution.

Les exigences non fonctionnelles sont souvent rédigées sous forme d'adjectifs (« le système doit être rapide »), ce qui les rend impossibles à tester. Les traduire en chiffres assortis d'un contexte — « latence au 95e percentile inférieure à 300 ms pour l'endpoint de recherche avec 200 utilisateurs simultanés sur la taille d'instance de production standard » — est de loin ce qu'une équipe peut faire de plus rentable pour la qualité non fonctionnelle, car cela transforme une opinion en contrôle réussi/échoué.

Boîte noire, boîte blanche, et entre les deux

Les tests boîte noire (fondés sur les spécifications) dérivent les cas de ce que le système est censé faire, sans aucune référence à la façon dont il est écrit. C'est la seule approche possible pour un composant tiers, et elle survit intacte aux refactorings : voilà pourquoi la plupart des tests utiles sont boîte noire vis-à-vis de leur sujet.

Les tests boîte blanche (fondés sur la structure) dérivent les cas du code : instructions, branches, conditions, chemins. Leur mesure est la couverture, c'est-à-dire la proportion d'instructions ou de branches exécutées. C'est la réflexion boîte blanche qui vous révèle qu'une suite n'exerce jamais la branche else d'un contrôle de validité.

Les techniques fondées sur l'expérience comblent les trous que les deux autres laissent : l'estimation d'erreurs (sonder les endroits où les défauts se logent historiquement — collections vides, zéro, Unicode, changements d'heure), les tests exploratoires (apprentissage, conception et exécution simultanés, en sessions limitées dans le temps) et les checklists tirées des incidents passés.

Dériver les cas plutôt que les deviner

Les techniques formelles de conception de tests servent à ramener un espace d'entrées infini à un petit ensemble de cas dont on peut justifier le choix. Quatre d'entre elles couvrent l'essentiel des besoins courants.

Partitions d'équivalence. Divisez chaque entrée en classes dont les membres devraient être traités de la même manière, puis testez un membre par classe. Pour un champ d'âge qui accepte 18 à 65, les partitions sont « moins de 18 », « de 18 à 65 » et « plus de 65 », plus des classes invalides : non numérique, vide, absent. Trois ou quatre valeurs en remplacent des millions.

Analyse des valeurs limites. Les défauts se concentrent aux bords des partitions, parce que c'est là qu'on confond < et <=. Pour chaque limite, testez la valeur elle-même et ses voisines immédiates de part et d'autre : 17, 18, 19 et 64, 65, 66. C'est la technique la plus rentable par test écrit, ce qui explique qu'on la retrouve dans presque toutes les spécifications.

Tables de décision. Quand le comportement dépend d'une combinaison de conditions, mettez-les en tableau. Une règle de frais de port fondée sur le montant de la commande, le niveau du client et la destination comporte un nombre fini de combinaisons ; les écrire révèle celles que personne n'a spécifiées.

Commande ≥ 50Membre premiumNationalFrais de port
ouiindifférentouigratuits
ouiouinongratuits
ouinonnonforfait
nonouiouigratuits
nonnonindifférenttarif standard

Une table à quatre conditions binaires indépendantes compte seize lignes, et c'est généralement à ce stade que quelqu'un remarque que la règle est trop compliquée. Lorsque l'ensemble combinatoire complet n'est pas praticable, le test par paires (pairwise) couvre chaque paire de valeurs de paramètres avec bien moins de cas et attrape la majorité des défauts d'interaction.

Test des transitions d'état. Pour tout ce qui a un cycle de vie — une commande, un paiement, un abonnement —, dessinez les états et les événements autorisés. Testez ensuite chaque transition valide et, surtout, les transitions invalides : annuler une commande déjà expédiée, payer une facture déjà remboursée. C'est dans les transitions invalides que se cachent les défauts intéressants, car la spécification les mentionne rarement.

La pyramide et ses détracteurs

La pyramide des tests est une heuristique sur la composition d'une suite : beaucoup de tests rapides et isolés à la base, moins de tests d'intégration au milieu, une poignée de tests de bout en bout au sommet. Sa justification est économique : les tests du bas s'exécutent en quelques millisecondes, échouent pour une seule raison et sont rarement instables, tandis que ceux du haut sont lents, fragiles et peu explicites quand ils échouent.

Ses détracteurs soulèvent deux points légitimes. Premièrement, la forme est souvent appliquée au mauvais axe : ce qui compte n'est pas le framework utilisé mais l'isolation et la rapidité du test, et un test « unitaire » qui mocke tous ses collaborateurs peut passer alors que le système assemblé est cassé. Deuxièmement, dans un service qui n'est essentiellement que de la colle entre une base de données et un client HTTP, il y a très peu de logique pure à tester unitairement, et une forme renflée au milieu (parfois dessinée en trophée ou en nid d'abeilles) reflète mieux la réalité.

Ces deux critiques portent sur les proportions, pas sur le compromis sous-jacent, que personne ne conteste : des tests plus larges détectent davantage de types de problèmes et coûtent plus cher à exécuter et à maintenir. Le guide de stratégie d'automatisation montre comment choisir une forme adaptée à un système donné plutôt que d'en hériter une d'un schéma.

Du défaut détecté au défaut clôturé

Un rapport de défaut est un outil de communication, et un mauvais rapport fait perdre plus de temps que le défaut lui-même. L'essentiel : ce qui a été fait, ce qui s'est passé, ce qui était attendu, sur quel build et dans quel environnement, avec des logs ou une capture d'écran et la plus courte séquence qui le reproduit. On lui affecte ensuite deux attributs souvent confondus. La sévérité est l'impact technique : une perte de données est de sévérité élevée, peu importe qui l'a remarquée. La priorité est l'urgence métier de le corriger. Un décalage cosmétique sur la page d'inscription peut être de sévérité faible et de priorité haute ; un crash dans une fonctionnalité utilisée par trois personnes peut être l'inverse.

Le cycle de vie est alors une petite machine à états : nouveau, qualifié, assigné, corrigé, prêt pour re-test, vérifié, clôturé — avec des branches latérales pour rejeté, doublon et différé. Deux règles le gardent honnête : seule la personne qui a signalé le défaut (ou le test qui l'a détecté) le clôture, et chaque défaut corrigé reçoit un test de non-régression, pour que la même défaillance ne puisse pas revenir en silence.

Savoir quand commencer et quand s'arrêter

Les critères d'entrée énoncent ce qui doit être vrai avant le début d'une phase de test : le build se déploie, les smoke tests passent, les données de test sont chargées, l'environnement correspond à la configuration cible. Leur rôle est d'empêcher une équipe de perdre des jours à tester un build qui n'avait aucune chance de fonctionner.

Les critères de sortie énoncent ce qui doit être vrai pour déclarer la phase terminée. Les critères utiles sont précis et mesurables : tous les cas prévus exécutés, aucun défaut de sévérité élevée ouvert, couverture du nouveau code au-dessus d'un seuil convenu, tous les seuils non fonctionnels atteints, défauts connus encore ouverts documentés et acceptés. Les inutiles, ce sont « les tests sont terminés » ou « on n'a plus le temps » déguisés en critère. Dans un pipeline de livraison continue, ces critères cessent d'être un jalon de fin de phase pour devenir des quality gates automatisées : la même idée, appliquée par une machine plutôt que par une réunion. Le guide de l'intégration continue montre à quoi cela ressemble en pratique.

Points clés à retenir

  • Les tests échantillonnent le comportement : ils démontrent la présence de défaillances et ne peuvent jamais prouver leur absence ; la façon de choisir les cas compte donc bien plus que leur nombre.
  • Les défauts coûtent considérablement plus cher à mesure qu'on les découvre tard, ce qui constitue tout l'argument économique en faveur de tests automatisés exécutés tôt et souvent.
  • Les niveaux décrivent un périmètre — composant, intégration, système, acceptation — et chaque test doit avoir un sujet clairement identifié pour qu'un échec pointe vers un endroit précis.
  • Les exigences non fonctionnelles ne deviennent testables qu'une fois écrites sous forme de chiffres assortis d'un contexte ; un adjectif ne peut pas faire échouer un build.
  • Partitions d'équivalence, valeurs limites, tables de décision et transitions d'état ramènent un espace d'entrées infini à un ensemble de cas défendable ; ce sont les valeurs limites qui rentabilisent le plus vite l'effort.
  • Un vocabulaire précis — erreur, défaut ou défaillance, vérification ou validation, sévérité ou priorité — accélère la qualification et raccourcit les débats.

Sources et lectures complémentaires

Questions fréquentes

Les tests peuvent-ils prouver qu'un logiciel n'a aucun bug ?
Non. Les tests échantillonnent le comportement d'un programme sur des entrées choisies, alors que l'espace des entrées, des états et des séquencements possibles est pratiquement infini. Une suite qui passe montre que les défaillances que vous cherchiez ne se sont pas produites dans les conditions que vous avez créées. C'est une preuve précieuse, mais une preuve de présence, jamais d'absence : voilà pourquoi les taux de couverture et les builds au vert sont des signaux, pas des garanties.
Quelle est la différence entre vérification et validation ?
La vérification se demande si le produit a été construit conformément à sa spécification : le code fait-il ce que la conception prévoyait ? La validation se demande si cette spécification résout le vrai problème de l'utilisateur. Une fonctionnalité peut réussir toutes les vérifications et échouer à la validation parce que l'exigence était fausse. Les tests unitaires et d'intégration vérifient surtout ; la recette utilisateur, les démos et les sessions d'utilisabilité valident.
Assurance qualité et test, est-ce la même chose ?
Le test est une activité parmi d'autres au sein de l'assurance qualité. La QA englobe tout ce qui détermine la façon dont on prévient et détecte les défauts : revue des exigences, normes de codage, pair programming, analyse statique, definition of done, rétrospectives et amélioration des processus. Le test mesure le produit ; la QA agit sur le processus qui le fabrique. Une équipe peut tester beaucoup et avoir malgré tout une assurance qualité médiocre si rien ne change quand les défauts se répètent.
Combien de cas de test suffisent ?
Il n'existe pas de nombre absolu. Dérivez les cas de la spécification avec une technique comme les partitions d'équivalence et l'analyse des valeurs limites, ajoutez des cas pour les zones à risque connues et les défauts déjà rencontrés, et arrêtez-vous quand de nouveaux cas ne font plus bouger votre niveau de confiance. Mesurez ensuite le risque résiduel plutôt que le nombre : quelles exigences ne sont pas testées, quels chemins de code ne s'exécutent jamais, quels modes de défaillance n'ont jamais été simulés.