Deine ersten Testfälle schreiben

Erwartete Ergebnisse, negative Tests, Grenzwerte und Äquivalenzklassen.

Bisher hast du Requests geschickt und dir angesehen, was zurückkommt. Testen beginnt, wenn du vor dem Senden entscheidest, was zurückkommen sollte, und dann vergleichst. Diese Entscheidung, aufgeschrieben, ist ein Testfall. In dieser Lektion geht es darum, Testfälle zu schreiben und auszuwählen, welche du schreibst, denn du kannst nicht jeden möglichen Request senden.

Was ein Testfall enthält

Teams nutzen verschiedene Vorlagen, aber fast alle haben dieselben Teile:

TeilWas hineingehörtBeispiel
IDEin kurzer Verweis, damit man darüber sprechen kannPROD-GET-03
TitelWas geprüft wird, in einer ZeileUnbekannte Produkt-ID liefert 404
VorbedingungenWas vor dem Start gelten mussEs gibt kein Produkt mit der ID 9999
RequestMethode, URL, Header und BodyGET /api/Products/9999
Erwartetes ErgebnisStatus, wichtige Header, Body404, JSON-Body, dessen Nachricht die ID 9999 nennt
Tatsächliches ErgebnisWird beim Ausführen ausgefülltWie erwartet

Das erwartete Ergebnis ist der Teil, den viele schlecht schreiben. "Liefert einen Fehler" ist kein erwartetes Ergebnis, weil fast alles das erfüllt. Sei konkret beim Statuscode, bei den wichtigen Headern (meist Content-Type, manchmal Cache- oder Location-Header) und beim Body: welche Felder da sein müssen, mit welchen Typen und Werten. Wenn du nicht sagen kannst, was die richtige Antwort ist, hast du eine Frage für den Entwickler oder den Product Owner gefunden, und die lohnt sich, bevor du irgendetwas testest.

Woher kommt das erwartete Ergebnis? Aus der Dokumentation, aus dem Ticket, das die Funktion beschrieben hat, aus dem Verhalten ähnlicher Endpunkte derselben API und manchmal aus schlichtem gesunden Menschenverstand (ein Preis sollte nicht negativ sein). Wenn sich diese Quellen widersprechen, notier, welcher du gefolgt bist.

Positive und negative Tests

Ein positiver Test schickt einen gültigen Request und prüft, dass er funktioniert: Produkt 1 existiert, also liefert GET /api/Products/1 es. Ein negativer Test schickt etwas, das die API ablehnen sollte, und prüft, dass sie es ordentlich ablehnt: der richtige Status, eine hilfreiche Nachricht und keine Nebenwirkungen.

Anfänger schreiben vor allem positive Tests, weil sie der Funktionsbeschreibung folgen. Entwickler haben den positiven Pfad meist selbst ausprobiert, bevor sie die Arbeit übergeben. Bei den negativen findest du die Dinge. Als grobe Faustregel: Rechne für jeden Endpunkt, der Eingaben annimmt, mit mehr negativen als positiven Tests.

Äquivalenzklassen

Der Listen-Endpunkt GET /api/Products nimmt einen Parameter limit an. Die API-Referenz sagt, dass er die Seitengröße ist, von 1 bis 100. Es gibt unendlich viele Werte, die du schicken könntest. Äquivalenzklassenbildung heißt, die Eingaben zu gruppieren, die der Server gleich behandeln sollte, und aus jeder Gruppe einen Wert zu testen statt alle:

KlasseBeispielwertErwartet
Gültig, im Bereichlimit=10Zehn Produkte
Unterhalb des Bereichslimit=-5Abgelehnt oder auf das Minimum gesetzt
Oberhalb des Bereichslimit=500Abgelehnt oder auf 100 gesetzt
Keine Zahllimit=abcMit 400 abgelehnt
Keine Ganzzahllimit=2.5Mit 400 abgelehnt
Leerlimit=Als fehlend behandelt
Fehlendkein limitAlle Produkte, wie dokumentiert

Beachte die Einträge "abgelehnt oder gesetzt". Die Dokumentation sagt nicht, was außerhalb des Bereichs passiert, also gibt es zwei vernünftige Verhaltensweisen. Beide lassen sich vertreten. Was du testest, ist, dass die API eine davon konsistent umsetzt und weder abstürzt noch etwas Unsinniges zurückgibt.

Grenzwerte

Bugs häufen sich an den Rändern von Bereichen, weil Entwickler dort < schreiben, wo sie <= meinten. Grenzwertanalyse heißt, die Grenze und je einen Schritt daneben zu testen. Für einen Bereich von 1 bis 100 ergibt das 0, 1, 2, 99, 100 und 101. So geht diese API tatsächlich mit diesen und den anderen Klassen um. Der Header X-Per-Page nennt die Seitengröße, die der Server verwendet hat.

RequestStatusZurückgegebene ElementeX-Per-Page
?limit=020011
?limit=120011
?limit=-120011
?limit=10020020 (alle)100
?limit=10120020 (alle)100
?limit=abc2002020
?limit=2.52002020
?limit=2002020
GET/api/Products?limit=0

So liest du die Ergebnisse: Werte unter 1 werden auf 1 angehoben, Werte über 100 auf 100 begrenzt, und alles, was keine Ganzzahl ist, wird ignoriert, als fehle limit. Nichts davon steht irgendwo, aber es ist konsistent, also vermutlich Absicht. Zwei Dinge verdienen trotzdem eine Notiz in deinem Testbericht. Dass limit=0 ein Element liefert, überrascht; ein Client, der null Elemente anfordert (etwa um günstig die Gesamtzahl zu lesen), bekommt ein Produkt, das er nicht wollte. Und dass limit=abc stillschweigend ignoriert wird, heißt, dass ein Tippfehler im Client unbemerkt bleibt, wo ein 400 ihn erwischt hätte.

Hier steckt auch eine Lehre über Testdaten. Die API hat nur 20 Produkte, also liefern limit=100 und limit=101 denselben Body. Am Body konntest du nicht erkennen, dass 101 begrenzt wurde. Der Header hat es gezeigt. Wenn eine Grenze größer ist als deine Daten, such entweder ein anderes Signal wie dieses oder leg genug Datensätze an, um die Grenze zu erreichen.

Grenzen verstecken sich auch in IDs

Die Referenz führt die id in /api/Products/{id} als int32, eine 32-Bit-Ganzzahl mit dem Höchstwert 2.147.483.647. Das ist eine Grenze, die sich zu testen lohnt:

RequestStatusBody
/api/Products/20200Produkt 20, das letzte
/api/Products/21404JSON-Fehlermeldung
/api/Products/0404JSON-Fehlermeldung
/api/Products/-1404JSON-Fehlermeldung
/api/Products/2147483647404JSON-Fehlermeldung
/api/Products/2147483648404Leer
/api/Products/abc404Leer
/api/Products/1.5404Leer
/api/Products/01200Produkt 1

Zwei Dinge fallen auf. Alles, was kein gültiger int32 ist, bekommt ein 404 ganz ohne Body, während gültige, aber nicht vorhandene IDs einen JSON-Fehler bekommen. Und 01 wird als 1 akzeptiert. Letzteres ist hier harmlos, aber bei einer API, die Antworten pro URL cacht, würden /1 und /01 getrennt gecacht. Keines der beiden Ergebnisse ist eindeutig falsch. Beide sollte man kennen.

Ein Satz Testfälle für einen Endpunkt

Zusammengenommen sieht ein vernünftiger erster Satz Testfälle für GET /api/Products/{id} so aus. Das ist nicht alles, was du testen könntest, aber es deckt jede Klasse einmal und jede Grenze ab.

IDRequestErwartetes Ergebnis
PROD-GET-01GET /api/Products/1200, JSON, id ist 1, title ist ein nicht leerer String, price ist eine Zahl über 0
PROD-GET-02GET /api/Products/20200, die höchste Start-ID
PROD-GET-03GET /api/Products/21404, JSON-Fehler-Body, der die ID 21 nennt
PROD-GET-04GET /api/Products/0404, JSON-Fehler-Body
PROD-GET-05GET /api/Products/-1404, JSON-Fehler-Body
PROD-GET-06GET /api/Products/abc400 oder 404, mit JSON-Fehler-Body
PROD-GET-07GET /api/Products/2147483648400 oder 404, mit JSON-Fehler-Body
PROD-GET-08HEAD /api/Products/1Gleicher Status und gleiche Header wie GET, kein Body
PROD-GET-09GET /api/Products/1, zweimalBeide Male identische Bodies

PROD-GET-06 und 07 scheitern gegen die aktuelle API, weil der Body leer ist. PROD-GET-08 scheitert auch: HEAD /api/Products/1 liefert 405 Method Not Allowed, während die neueren Ressourcen wie /api/Books/{id}anmelden zum Anzeigen HEAD unterstützen. Ob HEAD hier erwartet wird, ist eine Frage für die Verantwortlichen der API. Das ist in Ordnung. Ein Testfall hält fest, was passieren sollte; ein scheiternder Testfall ist der Weg, herauszufinden, dass es nicht passiert. PROD-GET-02 hat eine Schwäche: Wenn jemand Produkt 20 löscht oder Produkt 21 anlegt, bedeutet er nicht mehr, was er sagt. Die nächste Lektion zeigt, wie du eigene Testdaten anlegst, damit Tests nicht von Datensätzen abhängen, die jemand anders kontrolliert.

Einen Bug-Report schreiben

Wenn ein Testfall scheitert, schreibst du ihn auf. Ein guter Bug-Report lässt einen Entwickler das Problem in unter einer Minute nachstellen, ohne dich etwas fragen zu müssen. Nimm die leeren 404er von oben als Beispiel und vergleich zuerst die beiden Antworten:

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

Ein Report könnte so lauten (hier auf Englisch, wie ihn viele Teams schreiben):

Bug-Report
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.

Was diesen Report nützlich macht:

  • Der Titel sagt, was falsch ist und wo, er ist also auch in einer Liste von fünfzig Tickets klar.
  • Die Schritte sind Befehle zum Einfügen. Kein "ruf den Produkt-Endpunkt mit einer schlechten ID auf".
  • Das erwartete Verhalten verweist auf eine Quelle (die Dokumentation), es ist also nicht nur deine Vorliebe.
  • Er zeigt das Muster statt eines einzelnen Beispiels. Drei Requests, einer davon funktionierend zum Vergleich, sagen mehr als ein scheiternder Request.
  • Er nennt die Auswirkung auf einen echten Client. Entwickler priorisieren nach Auswirkung, und "inkonsistent" allein wird selten behoben.
  • Er gibt Unsicherheit zu. Das Verhalten kann gewollt sein, und der Report sagt, was die Sache in beide Richtungen klären würde (den Code korrigieren oder die Dokumentation).

Übung

Wähl GET /api/Books/{id}anmelden zum Anzeigen oder GET /api/Countries/code/{code}anmelden zum Anzeigen. Schlag den Endpunkt in der API-Referenz nach und dann:

  • Liste die Äquivalenzklassen für den Pfadparameter auf.
  • Wähl die Grenzwerte. Denk bei Ländercodes an Länge und Groß- und Kleinschreibung.
  • Schreib mindestens acht Testfälle im Tabellenformat von oben, mit konkreten erwarteten Ergebnissen.
  • Führ sie mit curl aus und trag die tatsächlichen Ergebnisse ein.

Wenn sich etwas anders verhält, als du erwartet hast, schreib dafür einen Bug-Report mit der Struktur von oben, inklusive dessen, worin du dir nicht sicher bist.

Üben und weitergehen