Écrire vos premiers cas de test

Résultats attendus, tests négatifs, valeurs limites et classes d’équivalence.

Jusqu’ici, vous avez envoyé des requêtes et regardé ce qui revenait. Le test commence quand vous décidez, avant d’envoyer, ce qui devrait revenir, puis comparez. Cette décision mise par écrit, c’est un cas de test. Cette leçon porte sur la façon de les écrire, et de choisir lesquels écrire, car vous ne pouvez pas envoyer toutes les requêtes possibles.

Ce que contient un cas de test

Les équipes utilisent des modèles différents, mais presque tous ont les mêmes parties :

PartieCe qu’on y metExemple
IDUne référence courte pour pouvoir en parlerPROD-GET-03
TitreCe qui est vérifié, en une ligneUn ID de produit inconnu renvoie 404
PréconditionsCe qui doit être vrai avant de commencerAucun produit d’ID 9999 n’existe
RequêteMéthode, URL, en-têtes et corpsGET /api/Products/9999
Résultat attenduStatut, en-têtes importants, corps404, corps JSON dont le message nomme l’ID 9999
Résultat obtenuRempli à l’exécutionConforme

Le résultat attendu est la partie que l’on écrit mal. "Renvoie une erreur" n’est pas un résultat attendu, parce que presque tout le satisfait. Soyez précis sur le code de statut, les en-têtes qui comptent (en général Content-Type, parfois des en-têtes de cache ou de localisation) et le corps : quels champs doivent être présents, avec quels types et quelles valeurs. Si vous ne savez pas dire quelle est la bonne réponse, vous avez trouvé une question pour le développeur ou le product owner, et elle mérite d’être posée avant de tester quoi que ce soit.

D’où vient le résultat attendu ? De la documentation, du ticket qui décrivait la fonctionnalité, du comportement de points d’accès similaires de la même API, et parfois du simple bon sens (un prix ne devrait pas être négatif). Quand ces sources divergent, notez celle que vous avez suivie.

Tests positifs et négatifs

Un test positif envoie une requête valide et vérifie qu’elle fonctionne : le produit 1 existe, donc GET /api/Products/1 le renvoie. Un test négatif envoie quelque chose que l’API devrait refuser et vérifie qu’elle le refuse correctement : le bon statut, un message utile, et aucun effet de bord.

Les débutants écrivent surtout des tests positifs, parce qu’ils suivent la description de la fonctionnalité. Les développeurs ont généralement essayé eux-mêmes le chemin positif avant de livrer. C’est dans les tests négatifs qu’on trouve des choses. En gros, attendez-vous à écrire plus de tests négatifs que de positifs pour tout point d’accès qui prend des entrées.

Les classes d’équivalence

Le point d’accès de liste GET /api/Products accepte un paramètre limit. La référence de l’API dit que c’est la taille de page, de 1 à 100. Il y a une infinité de valeurs possibles. Le partitionnement en classes d’équivalence consiste à regrouper les entrées que le serveur devrait traiter de la même façon, et à tester une valeur de chaque groupe plutôt que toutes :

ClasseValeur d’exempleAttendu
Valide, dans la plagelimit=10Dix produits
Sous la plagelimit=-5Refusé, ou ramené au minimum
Au-dessus de la plagelimit=500Refusé, ou ramené à 100
Pas un nombrelimit=abcRefusé avec 400
Pas un entierlimit=2.5Refusé avec 400
Videlimit=Traité comme absent
Absentpas de limitTous les produits, comme documenté

Remarquez les entrées "refusé, ou ramené". La documentation ne dit pas ce qui se passe hors de la plage : il y a donc deux comportements raisonnables. Les deux se défendent. Ce que vous testez, c’est que l’API en applique un, de façon cohérente, sans planter ni renvoyer quelque chose d’absurde.

Les valeurs limites

Les bugs se concentrent aux bords des plages, parce que c’est là que les développeurs écrivent < quand ils voulaient <=. L’analyse des valeurs limites consiste à tester la limite et un pas de chaque côté. Pour une plage de 1 à 100, cela donne 0, 1, 2, 99, 100 et 101. Voici ce que fait réellement cette API avec ces valeurs et les autres classes. L’en-tête X-Per-Page indique la taille de page utilisée par le serveur.

RequêteStatutÉléments renvoyésX-Per-Page
?limit=020011
?limit=120011
?limit=-120011
?limit=10020020 (tous)100
?limit=10120020 (tous)100
?limit=abc2002020
?limit=2.52002020
?limit=2002020
GET/api/Products?limit=0

Lecture des résultats : les valeurs inférieures à 1 sont ramenées à 1, celles supérieures à 100 sont ramenées à 100, et tout ce qui n’est pas un entier est ignoré comme si limit était absent. Rien de tout cela n’est écrit, mais c’est cohérent, donc probablement voulu. Deux points méritent quand même une note dans votre rapport de test. Que limit=0 renvoie un élément surprend : un client qui demande zéro élément (pour lire le total à moindre coût, par exemple) reçoit un produit qu’il ne voulait pas. Et que limit=abc soit ignoré en silence signifie qu’une faute de frappe dans un client passe inaperçue, là où un 400 l’aurait attrapée.

Il y a aussi ici une leçon sur les données de test. L’API n’a que 20 produits, donc limit=100 et limit=101 renvoient le même corps. Le corps ne permettait pas de voir que 101 avait été ramené. L’en-tête, si. Quand une limite dépasse vos données, trouvez un autre signal de ce genre ou créez assez d’enregistrements pour l’atteindre.

Les limites se cachent aussi dans les ID

La référence indique que l’id de /api/Products/{id} est un int32, un entier sur 32 bits dont la plus grande valeur est 2 147 483 647. C’est une limite qui vaut d’être testée :

RequêteStatutCorps
/api/Products/20200Le produit 20, le dernier
/api/Products/21404Message d’erreur JSON
/api/Products/0404Message d’erreur JSON
/api/Products/-1404Message d’erreur JSON
/api/Products/2147483647404Message d’erreur JSON
/api/Products/2147483648404Vide
/api/Products/abc404Vide
/api/Products/1.5404Vide
/api/Products/01200Le produit 1

Deux choses ressortent. Tout ce qui n’est pas un int32 valide reçoit un 404 sans aucun corps, alors que les ID valides mais inexistants reçoivent une erreur JSON. Et 01 est accepté comme 1. Le second point est sans danger ici, mais sur une API qui met les réponses en cache par URL, /1 et /01 seraient mis en cache séparément. Aucun des deux résultats n’est clairement faux. Les deux valent la peine d’être connus.

Un jeu de cas de test pour un point d’accès

Au total, un premier jeu raisonnable de cas de test pour GET /api/Products/{id} ressemble à ceci. Ce n’est pas tout ce que vous pourriez tester, mais cela couvre chaque classe une fois et chaque limite.

IDRequêteRésultat attendu
PROD-GET-01GET /api/Products/1200, JSON, id vaut 1, title est une chaîne non vide, price est un nombre supérieur à 0
PROD-GET-02GET /api/Products/20200, le plus grand ID de départ
PROD-GET-03GET /api/Products/21404, corps d’erreur JSON nommant l’ID 21
PROD-GET-04GET /api/Products/0404, corps d’erreur JSON
PROD-GET-05GET /api/Products/-1404, corps d’erreur JSON
PROD-GET-06GET /api/Products/abc400 ou 404, avec un corps d’erreur JSON
PROD-GET-07GET /api/Products/2147483648400 ou 404, avec un corps d’erreur JSON
PROD-GET-08HEAD /api/Products/1Même statut et mêmes en-têtes que GET, sans corps
PROD-GET-09GET /api/Products/1, deux foisCorps identiques les deux fois

PROD-GET-06 et 07 échouent sur l’API actuelle, parce que le corps est vide. PROD-GET-08 échoue aussi : HEAD /api/Products/1 renvoie 405 Method Not Allowed, alors que les ressources plus récentes comme /api/Books/{id}connectez-vous pour voir acceptent HEAD. Savoir si HEAD est attendu ici est une question pour les responsables de l’API. Ce n’est pas grave. Un cas de test consigne ce qui devrait se passer ; un cas de test qui échoue, c’est la façon de découvrir que ce n’est pas le cas. PROD-GET-02 a un point faible : si quelqu’un supprime le produit 20 ou ajoute le produit 21, il ne veut plus dire ce qu’il dit. La leçon suivante montre comment créer vos propres données de test pour que les tests ne dépendent pas d’enregistrements contrôlés par quelqu’un d’autre.

Rédiger un rapport de bug

Quand un cas de test échoue, vous le documentez. Un bon rapport de bug permet à un développeur de reproduire le problème en moins d’une minute sans rien vous demander. En prenant pour exemple les 404 vides ci-dessus, comparez d’abord les deux réponses :

GET /api/Products/9999
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8

{"status":404,"error":"Not Found","message":"Product with ID 9999 does not exist."}
GET /api/Products/abc
HTTP/1.1 404 Not Found
Content-Length: 0

Un rapport pourrait dire (ici en anglais, comme beaucoup d’équipes les rédigent) :

Rapport de bug
Title: GET /api/Products/{id} returns an empty 404 for non-numeric
       and out-of-range IDs, instead of the JSON error body

Environment: https://api.testingapis.com, 30 Sep 2026

Steps:
  curl -i https://api.testingapis.com/api/Products/9999
  curl -i https://api.testingapis.com/api/Products/abc
  curl -i https://api.testingapis.com/api/Products/2147483648

Expected:
  All three return 404 with Content-Type: application/json and a body
  like {"status":404,"error":"Not Found","message":"..."}, as documented
  under "Responses and errors" in the API reference.

Actual:
  9999        404, JSON body with a message
  abc         404, Content-Length: 0, no Content-Type, no body
  2147483648  404, empty body (one above the int32 maximum; 2147483647
              returns the JSON error)

Impact:
  Clients that parse every error body as JSON fail on the empty
  responses. The empty 404 also looks like "this URL does not exist"
  rather than "this product does not exist".

Notes:
  Probably the route only matches int32 values, so other input never
  reaches the controller. Could be intended; if so, the docs should say
  which errors have no body.

Ce qui rend ce rapport utile :

  • Le titre dit ce qui ne va pas et où, il reste donc clair dans une liste de cinquante tickets.
  • Les étapes sont des commandes à coller. Pas de "appelez le point d’accès produits avec un mauvais ID".
  • Le comportement attendu renvoie à une source (la documentation), ce n’est donc pas qu’une préférence personnelle.
  • Il montre le schéma plutôt qu’un seul exemple. Trois requêtes, dont une qui marche pour comparer, en disent plus qu’une requête qui échoue.
  • Il indique l’impact sur un vrai client. Les développeurs priorisent selon l’impact, et "incohérent" tout seul est rarement corrigé.
  • Il admet l’incertitude. Le comportement peut être voulu, et le rapport dit ce qui réglerait la question dans un sens comme dans l’autre (corriger le code ou corriger la documentation).

Exercice

Choisissez GET /api/Books/{id}connectez-vous pour voir ou GET /api/Countries/code/{code}connectez-vous pour voir. Cherchez le point d’accès dans la référence de l’API, puis :

  • Listez les classes d’équivalence du paramètre de chemin.
  • Choisissez les valeurs limites. Pour les codes pays, pensez à la longueur et à la casse.
  • Écrivez au moins huit cas de test au format du tableau ci-dessus, avec des résultats attendus précis.
  • Lancez-les avec curl et remplissez les résultats obtenus.

Si quelque chose se comporte autrement que prévu, rédigez un rapport de bug selon la structure ci-dessus, en incluant ce dont vous n’êtes pas sûr.

S’entraîner et aller plus loin