Conception de tests
Les cas déterminent ce que la suite peut détecter. Tout ce qui vient après — le harnais, l'exécuteur, le décompte — ne fait qu'exécuter cette décision.
Les moments que ce skill remplace
| Sur le point de… | Au lieu de cela… |
|---|---|
| écrire le chemin heureux et s'arrêter | parcourir l'espace d'entrée : pour chaque paramètre, ses classes et ses limites |
| déduire les cas de l'implémentation | les déduire de la spécification, et laisser l'implémentation vous surprendre |
| tester avec une valeur confortable | tester la dernière valeur à l'intérieur et la première valeur à l'extérieur de chaque frontière |
| tester la limite que la spécification nomme | toute plage a deux extrémités ; celle que la phrase a omise est toujours une frontière |
| énumérer les rôles listés par la spécification | ajouter l'appelant qu'elle a oublié : aucun rôle, non authentifié, non attribué |
| faire varier une condition à partir du chemin heureux | construire la table de décision et couvrir les règles où deux conditions échouent ensemble |
| dessiner une table d'état et passer à autre chose | mettre un identifiant de test dans chaque ligne ; la ligne qui dit « non autorisé » en priorité |
| écrire « rejeté, ou accepté à condition que… » comme résultat attendu | un résultat par cas ; une spécification qui ne peut pas décider est une question ouverte, pas un test |
| ignorer les chemins d'erreur comme improbables | chaque mode de défaillance est une classe : entrée invalide, ressource manquante, refus, délai d'attente, à moitié terminé |
| laisser un chemin non implémenté sans test, avec un marqueur de saut ou un stub sans assertion | écrire un test qui vérifie que le chemin est refusé comme non implémenté aujourd'hui ; il passe maintenant et devient rouge le jour où quelqu'un l'implémente (ci-dessous) |
| tester une fonctionnalité par son flux principal | lire les flux alternatifs et d'exception du cas d'utilisation ; chacun est une classe |
| évaluer une suite en la lisant | la mesurer par rapport à la spécification : classes, les deux limites, règles, transitions |
| ajouter un cinquième test à une classe déjà couverte | le consacrer à une classe non couverte — le cinquième achète du temps d'exécution et aucune couverture |
Attaquer la spécification, pas l'implémentation
Un test écrit en lisant le code vérifie ce que le code fait. Il passe à l'arrivée, encode les bugs actuels comme des attentes, et ses échecs après un changement disent « le code a changé », ce qui était déjà connu. Ceci est mesuré, pas suspecté : sur 24 dépôts Java, les oracles générés par LLM avaient tendance à capturer le comportement réel du programme plutôt que son comportement attendu (Konstantinou, Degiovanni, Papadakis, arXiv:2410.21136).
Déduisez de ce que la chose est censée faire — la spécification, le format filaire, le contrat — et seulement ensuite exécutez. La seule exception légitime est le test de caractérisation qui épingle délibérément le comportement actuel, et il est marqué comme tel pour que personne ne confonde plus tard un bug épinglé avec une exigence.
Un indicateur utile : si chaque test était vert lors de sa première exécution, alors la suite a été déduite de l'implémentation, quelle qu'ait été l'intention.
Classes d'équivalence : compter les classes, pas les tests
Les entrées que le code ne peut pas distinguer forment une classe, et un test par classe suffit — du deuxième au cinquième, ils coûtent du temps d'exécution et ne prouvent rien de nouveau. La discipline fonctionne dans les deux sens : aucune classe laissée non couverte, aucune classe couverte deux fois par accident.
Une suite de dix-sept tests semble exhaustive. Mais cela dépend du nombre de classes dans lesquelles ces dix-sept tests tombent, et ce nombre n'apparaît dans la sortie d'aucun exécuteur. Il doit être compté manuellement par rapport à l'espace d'entrée — ce qu'est un plan de test.
Les classes que la spécification liste sont la moitié facile. Une table de rôles avec trois lignes a une quatrième classe, la requête qui ne correspond à aucune d'elles, et c'est celle qu'une suite construite à partir de la table ne contient jamais. Pour chaque paramètre, écrivez les classes invalides à côté des classes valides avant d'écrire un cas : mauvais type, absent, en dehors de toute valeur listée, l'appelant sans aucun droit.
Le syllabus énonce ceci comme une règle plutôt qu'une habitude : dans le partitionnement d'équivalence, les éléments de couverture sont les partitions, 100% signifie que chaque partition identifiée est exercée « y compris les partitions invalides », et les partitions « ne doivent pas se chevaucher et doivent être des ensembles non vides » (ISTQB CTFL v4.0.1 §4.2.1). Avec plusieurs paramètres, il nomme le critère le plus faible Chaque Choix — chaque partition de chaque paramètre une fois, les combinaisons ignorées — qui est le niveau que la plupart des suites atteignent sans connaître son nom, et là où la table de décision ci-dessous commence.
Limites, parce que c'est là que se trouvent les défauts
L'erreur de décalage d'un est le défaut arithmétique le plus courant,
et il est invisible partout sauf à la limite.
La paire qui compte à chaque limite est la valeur limite et son plus proche voisin de l'autre côté de la ligne,
ce qui est l'analyse de valeurs limites (BVA) à 2 valeurs (CTFL v4.0.1 §4.2.2) ;
la BVA à 3 valeurs ajoute le voisin à l'intérieur aussi,
et le syllabus montre ce que cela apporte :
une vérification prévue comme x ≤ 10 mais écrite comme x == 10 passe à la fois pour 10 et 11,
et seul 9 la détecte.
Les limites récurrentes : vide, exactement un, plusieurs ; zéro et négatif ;
le maximum déclaré et un au-delà ;
la valeur qui est exactement le tampon, la limite, le délai d'attente.
Un exemple concret de cette machine : une vérification de chemin qui fonctionne à 8 190 caractères et doit échouer à 8 192 doit être testée à ces deux valeurs. Passer à 4 000 et 12 000 n'établit presque rien sur la ligne entre elles.
Lorsque la limite est une précondition énoncée, la valeur au-delà est un bogue de l'appelant plutôt qu'un cas avec un résultat attendu ; la « conception par contrat » décide lequel des deux une limite donnée est, et le test vérifie ensuite celui qui s'applique.
Le côté que la spécification n'a pas nommé
« Jusqu'à 10 Mo » nomme une extrémité d'une plage. L'autre extrémité existe que la phrase la mentionne ou non, et les cas déduits du texte s'arrêtent là où le texte s'arrête : une suite avec la limite supérieure testée à −1, 0 et +1 octets et rien en dessous de 1 024 a effectué une analyse de limites sur la moitié d'une plage. La définition n'a pas de côté : « les valeurs minimales et maximales d'une partition sont ses valeurs limites » (CTFL v4.0.1 §4.2.2). Écrivez les deux pour chaque partition d'abord, puis prenez chacune avec son plus proche voisin dans la partition adjacente. Le côté non nommé est généralement zéro, vide ou un, et la spécification est généralement silencieuse à ce sujet parce que personne n'a décidé.
Déplacer deux conditions en même temps
Une suite construite en changeant une condition du chemin heureux couvre chaque valeur de chaque condition et presque aucune de leurs combinaisons : format erroné, taille dépassée, nombre dépassé, chacun isolé, et aucun cas où deux échouent ensemble. Quelle erreur l'emporte lorsque les deux s'appliquent est rarement dans la spécification non plus, donc le cas manquant cache une décision manquante.
Disposez les conditions comme une table de décision et comptez les règles, puis couvrez les règles où deux conditions échouent en même temps et la règle où une restriction de rôle et une restriction de format s'appliquent toutes les deux. Dans le test par table de décision, les éléments de couverture sont les colonnes contenant les combinaisons réalisables, et 100% signifie que chaque colonne de ce type est exercée (CTFL v4.0.1 §4.2.3) ; le syllabus ajoute que la table « aide à trouver les lacunes ou contradictions dans les exigences », ce qui est la décision manquante ci-dessus qui fait surface d'elle-même. Le même document peut sembler entièrement couvert sous Chaque Choix et au quart couvert sous les colonnes, et seul le deuxième nombre dit quoi que ce soit sur les combinaisons. Lorsque le nombre de règles est le produit d'un ensemble de drapeaux, la correction la moins chère est en amont : « d'abord l'état » réduit les drapeaux aux états qui peuvent se produire, et la table se réduit à eux.
Une table est une affirmation sur la suite
Une table d'état, une table de décision, la liste des flux d'un cas d'utilisation : chaque ligne affirme qu'un test existe pour elle. Mettez l'identifiant du test dans la ligne. Une ligne sans identifiant est une classe non couverte, et la ligne qui dit « non autorisé » est celle qui a le plus de chances d'être vide, car écrire l'interdiction a semblé suffisant pour la traiter. La transition interdite — le deuxième envoi alors qu'un est en cours — est le test, et la garde qui est censée l'arrêter est la chose testée.
La table d'état rend cette ligne explicite : une transition invalide est une cellule vide, et le plus fort des trois critères que le syllabus définit, la couverture de toutes les transitions, exige de tenter chacune d'elles — un cas de test par transition, afin qu'un défaut ne puisse pas en masquer un autre (CTFL v4.0.1 §4.2.4). La couverture des transitions valides, le commutateur-0 là où la plupart des suites s'arrêtent, ne touche jamais ces cellules, ce qui explique comment une ligne « non autorisé » non testée reste verte.
Ensuite, lisez les deux documents l'un contre l'autre. Un test dont le chemin est en désaccord avec la table (la table dit échec → envoi, le test va échec → sélectionné → envoi) signifie que l'un d'eux est erroné, et rien d'autre que la comparaison ne permet de découvrir lequel. Les auto-transitions et les transitions hors de l'état d'échec comptent aussi comme des lignes.
Un résultat par cas
« Rejeté, ou si accepté, aucune donnée corrompue ne subsiste après l'envoi » ne peut ni passer ni échouer. Il apparaît honnêtement, quand la spécification n'a jamais dit ce qu'il advient d'un fichier de zéro octet et que l'auteur a refusé d'inventer une réponse, et il survit à la relecture parce que le cas est visiblement présent. La disjonction se cache dans la colonne « attendu ». La forme correcte est une question au propriétaire de la spécification, listée comme telle, et pas de test pour cette classe tant qu'elle n'est pas répondue.
Les chemins d'erreur sont aussi des classes
Les chemins malheureux sont là où le code non testé s'accumule, car rien ne les exerce en utilisation normale — un détenteur de verrou qui meurt en cours d'exécution, un dossier mappé qui n'existe pas, une entrée avec le champ attendu manquant. Chacun est une classe d'équivalence avec la même prétention à un test que n'importe quelle plage d'entrée. Une suite qui prouve seulement que l'outil fonctionne quand tout coopère a testé la démo, pas l'outil.
Les cas d'utilisation énumèrent les classes avant qu'il y ait du code
Un cas d'utilisation est une spécification déjà découpée en classes : un flux principal, chaque flux alternatif, chaque flux d'exception, et la précondition sous laquelle chacun peut être entré. Quiconque l'a écrit a fait l'énumération que ce skill demande — les flux alternatifs et d'exception sont les chemins d'erreur — donc une suite qui exerce le flux principal a couvert une classe parmi les N que le document liste.
Lisez les flux du cas d'utilisation et donnez à chacun son test, puis vérifiez les préconditions, car un flux n'est accessible que lorsque sa précondition peut être vraie. Une vérification de parité pour des pages traduites a un flux principal (les deux langues éditées et soumises), un flux alternatif (une modification unilatérale déclarée comme exception), et deux flux d'exception qui n'apparaissent qu'une fois les préconditions écrites : l'arborescence est sale, donc l'obsolescence ne peut pas encore être jugée ; l'historique a été réécrit, donc un commit déclaré ne se résout plus. Les deux ont été rencontrés sur un site en une semaine. Le chemin heureux seul n'aurait montré aucun des deux.
Qu'un flux d'exception puisse réellement se produire n'est pas déterminé par son improbabilité apparente. L'enveloppe opérationnelle dit ce qui est hors de portée et l'analyse des dangers dit ce qui reste ; les deux vivent dans « dossier-d'assurance », et ce skill prend la liste qu'ils produisent au lieu de la tailler par intuition.
La couverture est un nombre sous un critère nommé
Un document de test mesuré de trois façons : 100% des conditions, 90,9% MC/DC, 25% des règles de décision. Les trois sont vrais pour le même fichier, donc un chiffre de couverture sans son critère ne dit rien, et un chiffre dont le critère est choisi après le comptage dit encore moins. Nommez-le avant de compter, dans les propres termes du syllabus pour que le nombre puisse être vérifié : couverture de partition et Chaque Choix (§4.2.1), BVA à 2 ou 3 valeurs (§4.2.2), colonnes de table de décision (§4.2.3), tous les états, transitions valides ou toutes les transitions (§4.2.4). MC/DC ne figure pas dans le syllabus Fondation ; il vient des normes de sécurité (DO-178C, ISO 26262), et le lister à côté des autres sans le dire est le genre de citation mixte qui passe pour de la rigueur. Le reste de ce qu'un nombre doit à son lecteur se trouve dans « affirmations-mesurées ».
Vérifier une suite : compter, ne pas juger
Demander si une suite est adéquate renvoie les angles morts qui l'ont produite. Une deuxième session sur le même modèle les partage, et il en va de même pour la même personne un jour plus tard. Le syllabus appelle cela « une certaine indépendance », le deuxième de ses quatre niveaux, et dit d'où vient la valeur : « les différences entre les biais cognitifs de l'auteur et du testeur » (CTFL v4.0.1 §1.5.3). Une deuxième session sur le même modèle partage les biais et ne garde que l'étiquette. L'étape qui ne les partage pas est de compter : listez les classes de la spécification et cochez celles qui ont un test, listez les deux limites de chaque plage, listez les règles de la table de décision, listez les lignes de chaque table d'état, et citez le fichier et la ligne de chaque lacune. Qu'une valeur attendue soit correcte reste un jugement et reste avec un lecteur qui ne l'a pas écrite ; qu'une classe ait un test est de l'arithmétique. Si le comptage a été échantillonné plutôt qu'exhaustif, le rapport dit échantillonné. Le formulaire de réfutation par défaut pour le verdict se trouve dans « vérification-adversaire » ; ce skill fournit ce qu'il faut compter.
Quand la demande supprime une classe
« Le double envoi ne peut pas se produire parce que le bouton est désactivé, supprimez cette ligne » supprime une classe de la suite en s'appuyant sur une seule garde dans une seule couche. Faites ce qui est demandé, et dites en une phrase quelle classe est maintenant non couverte et ce qui pourrait l'atteindre : la machine d'état accepte toujours un deuxième envoi de la part de tout appelant qui n'est pas le bouton. Une ligne supprimée avec cette phrase est une décision ; une ligne supprimée sans elle est une lacune que personne ne retrouvera.
Un chemin non implémenté a un test qui le dit
Un chemin laissé de côté intentionnellement dans cette modification,
un rôle que cette version ne prend pas en charge, un format pas encore analysé,
une branche qui lève NotImplementedException ou appelle todo!(),
reste une classe de l'entrée,
et la suite enregistre ce qui s'y passe aujourd'hui.
Écrivez le test qui vérifie que le chemin est refusé comme non implémenté :
l'exception ou la valeur d'erreur qu'il soulève,
l'élément de menu qui est absent, la requête qui répond « non pris en charge ».
Ce test passe maintenant et fait deux choses. Il place la lacune dans la liste de la suite, où un lecteur qui compte les classes la trouve, au lieu d'être dans un commentaire ou un ticket. Et il devient rouge le jour où quelqu'un implémente le chemin, ce qui est le moment où ses vrais cas doivent être écrits ; le test qui échoue est le rappel, et il est remplacé, pas supprimé.
Un marqueur de saut ou un test en attente n'est pas un test — c'est une promesse que le test existera un jour, et les promesses ne deviennent pas rouges.