Anfrage-Bodys und eine JSON-API · Übung
Beweise, dass PUT idempotent ist
In Kapitel 2 hast du zwei Versprechen der PUT ist PUT zweimal zu senden hinterlässt den Server im selben Zustand wie einmal. POST ist es nicht: Ein zweites identisches POST fügt ein zweites Buch hinzu. Ein Client verlässt sich auf diese Versprechen, wenn eine Antwort verloren geht und er entscheiden muss, ob er die Anfrage noch einmal sendet. Dein Server nimmt jetzt beide Methoden an, und noch zeigt nichts, dass er eins der Versprechen hält. Diese Lektion beweist beide mit zwei eigenen Tests.
Vergleiche Zustände, nicht Antworten
Du hast in Kapitel 2 gesehen, dass ein wiederholtes DELETE idempotent ist, obwohl sich seine beiden Antworten unterscheiden, 204 und dann 404: Das Versprechen betrifft den Zustand auf dem Server, nicht das, was zurückkommt. Ein Test dieses Versprechens muss sich den Zustand ansehen.
Dafür braucht es hier keinen Server, weil handle den Zustand als reading_server.sample_state() einen frischen Zustand, sende ein PUT und halte fest, wie der Zustand aussieht; sende dasselbe PUT noch einmal und vergleiche:
state = reading_server.sample_state()
data = {"title": "Dune", "author": "Frank Herbert"}
reading_server.handle(make_request("PUT", "/books/2", data), state)
books_after_one = len(state["books"])
Arrange, Act, Assert, wie in jedem Test, den du geschrieben hast: Hier ist das Act die zweite Anfrage, und die Assertions vergleichen. Zwei Vergleiche sagen, was idempotent für diesen Server bedeutet. Die Anzahl der Bücher nach zwei PUTs ist gleich der Anzahl nach einem. Und das Buch, das die state["books"][2], ist nach beiden dasselbe.
Was es nicht beweisen würde, sind zwei Status. Ein Server, dessen PUT jedes Mal stillschweigend ein neues Buch speichert, würde trotzdem zweimal 200 OK antworten. Ein Test, der nur die Antworten vergleicht, besteht auch für diesen Server und beweist also nichts über das Versprechen.
make_request(method, target, data) oben in tests/test_server.py baut das Anfrage-data kodiert es die Daten als content-type: application/json und ein nach Bytes gezähltes content-length hinzu, so wie read_body sie hinterlassen hätte.
Zwei POSTs, zwei Bücher
Der POST-Test prüft das gegenteilige Versprechen: Zwei identische Anfragen POST /books legen zwei Bücher an. Ein frischer Beispielzustand hat zwei Bücher, also sollte er nach den zwei Anfragen vier haben. Die beiden Antworten sagen in location, wohin jedes Buch gekommen ist, und die müssen sich unterscheiden: zwei Bücher, zwei IDs.
first = reading_server.handle(make_request("POST", "/books", data), state)
first["headers"]["location"] ist der Pfad des ersten neuen Buchs. Ein Server, dessen POST dieselbe ID zweimal vergibt, würde das zweite Buch über das erste speichern, beide Male 201 Created antworten und zweimal dasselbe location senden. Bücher zu zählen oder die beiden Locations zu vergleichen, deckt das auf.
Warum die Tests reading_server.handle aufrufen
Deine Tests werden selbst geprüft. Check my work führt sie gegen andere Versionen von handle aus: eine, die sich korrekt verhält, und kaputte, die ein Versprechen halten, aber nicht das andere. Es setzt jede Version ein, indem es reading_server.handle ersetzt. Ein Test, der reading_server.handle(...) aufruft, schlägt die from reading_server import handle sagt oder handle = reading_server.handle, hält die ursprüngliche Funktion unter einem eigenen Namen fest, und der Austausch erreicht sie nie. Deshalb sagt dir der Docstring der Datei, dass du sie über das Modul aufrufen sollst. Eine eigene Hilfsfunktion in der Testdatei, die reading_server.handle(...) aufruft, ist auch in Ordnung: Sie schlägt die Funktion ebenfalls bei jedem Lauf nach.
Die korrekte Version, die Check my work verwendet, ist seine eigene, nicht deine, also können ihre Fehlermeldungen anders formuliert sein als deine. Teste Status, Header und den Zustand, nicht den Wortlaut eines Fehlers.
Welche Assertion deckt ein PUT auf, das jedes Mal ein neues Buch hinzufügt?
Deine Aufgabe
Ersetze in tests/test_server.py die beiden Platzhalter, die assert False, "not written yet" sagen:
test_repeating_a_put_leaves_one_state: Sende dasselbePUT /books/2zweimal an einen frischenreading_server.sample_state()und stelle sicher, dass das zweite die Anzahl der Bücher und Buch 2 genau so hinterlassen hat wie das erste.test_repeating_a_post_creates_two_books: Sende dasselbePOST /bookszweimal an einen frischen Zustand und stelle sicher, dass der Zustand danach vier Bücher enthält und dass sich die beidenlocation-unterscheiden.
Behalte test_get_books_is_json, behalte import reading_server und sende jede Anfrage über reading_server.handle(...), innerhalb jedes Tests oder in einer eigenen Hilfsfunktion in derselben Datei. Führ dann die Tests im Tab Shell aus:
python -m pytest -q
Es sollte 3 passed melden. Drück dann Check my work. Das Projekt am Ende dieses Kapitels fügt der Leseliste noch eine Funktion hinzu und beweist alles davon.
Aufgabe
Beweise mit eigenen Tests, dass PUT POST nicht.
Ersetze in tests/test_server.py die beiden Platzhalter:
test_repeating_a_put_leaves_one_state: Sende dasselbePUT /books/2mit demselben Body zweimal an einen frischenreading_server.sample_state(). Stelle sicher, dass nach der zweiten Anfrage die Anzahl der Bücher und Buch 2 genau so sind wie nach der ersten.test_repeating_a_post_creates_two_books: Sende dasselbePOST /bookszweimal an einen frischen Zustand. Stelle sicher, dass der Zustand danach zwei Bücher mehr enthält als am Anfang und dass sich dielocation-der beiden Antworten unterscheiden.
Bau Anfragen mit dem vorhandenen make_request(method, target, data). Behalte test_get_books_is_json und import reading_server und sende jede Anfrage über reading_server.handle(...), innerhalb jedes Tests oder in einer eigenen Hilfsfunktion in derselben Datei; füge keine Zeile from reading_server import hinzu und speichere handle nicht oben in der Datei unter einem Namen. Teste Status, Header und den Zustand, nicht den Wortlaut von Fehlermeldungen.
Im python -m pytest -q die Tests aus; wenn beide geschrieben sind, meldet es 3 passed.
Drück dann Check my work. Es führt deine Tests dreimal aus, jedes Mal mit einem anderen handle, das über das PUT den Body bei jedem Aufruf unter einer neuen ID speichert, und einem, dessen POST nie zur nächsten ID weiterrückt, sodass ein zweites POST das erste überschreibt. Deine Tests müssen gegen das erste alle bestehen, und gegen jedes der anderen muss mindestens einer fehlschlagen. Ein Lauf, der länger als 3 Sekunden dauert, wird abgebrochen und gemeldet. Nichts aus deinem Terminal wird bewertet.