0%

Der Standard-Client und die Statusklassen · Abschlussprojekt

Abschlussprojekt: Projekt: ein Client mit der Standardbibliothek

get_json, classify und report funktionieren, und du hast sie Szenario für Szenario gegen practice-api beobachtet. Ein Lauf gegen einen echten Dienst zeigt, dass ein Fall einmal funktioniert hat. Tests auf einer Ersatz-Verbindung zeigen mehr: jede Klasse, die Fehler-Bodys, ein Timeout und das Schließen, jedes Mal, wenn du sie ausführst, ohne dass ein Server nötig ist. Dieses Projekt stellt den Client fertig und prüft ihn mit deinen eigenen Tests.

Was der Client verspricht

Wenn dieses Projekt fertig ist, hält wire.py diese Versprechen, und deine Tests prüfen sie:

  • get_json gibt für eine Antwort jeder Klasse den vollständigen Datensatz zurück: "status", "reason", "headers", "content_type", "retry_after", "body", "class" und "data".

  • Der Body jedes 4xx und 5xx wird behalten und in "data" dekodiert, wenn er als ausgezeichnet ist.

  • Wenn keine Antwort ankommt, verlässt TimeoutError get_json, und die Verbindung ist geschlossen.

  • report macht aus einem Timeout eine Zeile, die keinen Status nennt, und lässt jeden anderen Fehlschlag durch.

Wenn eines davon noch nicht stimmt, behebe es zuerst. Wenn die Lektionen 1 bis 5 alle bestanden wurden, braucht dein Code vielleicht keine Änderung.

Die beiden Fakes

Öffne fakes.py und lies die beiden nach fake_connect. scripted_response(status, reason, header_pairs, body) baut eine Antwort: header_pairs ist eine von Listen [name, value] in der Reihenfolge, in der ein Server sie senden würde, und getheader findet einen Namen in beliebiger Schreibweise, wie das echte. fake_make_connection(response, calls) gibt einen Ersatz für http.client.HTTPConnection zurück. Übergib ihn als make_connection=, und jeder Schritt, den dein Code macht, landet der Reihe nach in calls:

{"call": "connect", "host": "practice-api", "port": 8080, "timeout": 2.0}
{"call": "request", "method": "GET", "url": "/v1/items/1", "body": None, "headers": {"X-Practice-Name": "http-with-python"}}
{"call": "close"}

Mit None als Antwort löst getresponse() TimeoutError aus, so wie ein stiller Server es beim echten bewirkt. Alles, was ein Test braucht, ist da: ein geskripteter Status, Header in beliebiger Schreibweise, ein Body und eine Aufzeichnung darüber, ob die Verbindung geschlossen wurde.

Ein Test, Schritt für Schritt

Hier ist die Form eines Tests für einen Erfolg, mit den ersten beiden Zusicherungen ausgeschrieben:

def test_get_json_decodes_a_success():
    calls = []
    response = scripted_response(200, "OK", [["Content-Type", "application/json"]], b'{"id": 1}')
    record = wire.get_json("/v1/items/1", make_connection=fake_make_connection(response, calls))
    assert record["class"] == "success"
    assert record["data"] == {"id": 1}

Bereite eine Antwort und eine leere Liste calls vor, handle, indem du wire.get_json aufrufst, und prüfe dann mit assert den Datensatz und calls. Füge fake_make_connection und scripted_response der Zeile from fakes import oben in tests/test_wire.py hinzu.

Für einen Test, der eine erwartet, hat die Datei das Muster schon: test_a_close_inside_the_body_is_an_error ruft die Funktion innerhalb von try auf, kehrt aus dem except-Zweig für die erwartete Ausnahme zurück und löst nach dem try AssertionError aus, wenn der Aufruf stattdessen zurückgekehrt ist. calls[-1] ist der letzte aufgezeichnete Schritt, und dorthin gehört ein Schließen.

Meilensteine

  1. Ein Erfolgstest wie der oben, der außerdem den Eintrag connect in calls prüft und dass der letzte Eintrag das Schließen ist.

  2. Ein Test für ein 4xx und einer für ein 5xx, jeder mit einem JSON-Fehler-Body, die die Klasse und das dekodierte "data" prüfen.

  3. Ein Test mit fake_make_connection(None, calls), der TimeoutError von get_json erwartet und dann prüft, dass calls mit dem Schließen endet.

  4. Ein Test für report: Ein geskriptetes 504 ergibt eine Zeile, die mit 504 beginnt, und ein stiller Server ergibt eine Zeile, die timed out sagt und kein 504 enthält.

  5. Alles andere, was sich zu prüfen lohnt, etwa Retry-After in seltsamer Schreibweise oder classify mit einem Code, den keine deiner Listen nennt.

Schreibe jeden Test als einfache Funktion def test_...(): ohne , so wie die anderen Tests der Datei. Behalte import wire und rufe in jedem Test wire.get_json(...), wire.report(...) und wire.classify(...) auf; füge keine Zeile from wire import hinzu und speichere diese Funktionen nicht oben in der Datei unter eigenen Namen. Check my work setzt über das andere Versionen dieser Funktionen ein, um zu sehen, ob deine Tests es bemerken, und ein mit from importierter Name würde das Original behalten.

Führ es aus

Führe die Tests im Projektordner aus:

python -m pytest -q

Jeder Test sollte bestehen, auch die aus den Kapiteln 1 und 2, und keiner davon braucht practice-api. Wenn du möchtest, führe report auch über die Routen in README.md aus; dieser Lauf ist für dich und wird nie bewertet:

python -c "
import wire
for target in ['/v1/items?page=1&page_size=2', '/v1/scenarios/rate-limit?attempt=3', '/v1/items/99', '/v1/scenarios/server-error', '/v1/scenarios/timeout?delay_ms=750']:
    print(target, wire.report(target, timeout=0.5))
"

Die ersten vier Zeilen nennen einen Status und seine Klasse: 200 success, dann 429 client error, 404 client error und 500 server error. Die letzte sagt, dass das Timeout deines Clients abgelaufen ist, weil eine halbe Sekunde kürzer ist als die 750 Millisekunden des Szenarios.

Drücke dann Check my work. Dein Client liest jetzt jede Antwort, die practice-api senden kann, und sagt, was passiert ist, wenn keine kommt. In Kapitel 4 dreht sich die Verbindung um: Du schreibst den Server, und wire.py wird zum Werkzeug, das dir genau zeigt, was dein Server gesendet hat.

Aufgabe

Stelle den Client mit der Standardbibliothek fertig und prüfe ihn mit deinen eigenen Tests.

Sorge in wire.py dafür, dass get_json für eine Antwort jeder Klasse den vollständigen Datensatz zurückgibt ("status", "reason", "headers", "content_type", "retry_after", "body", "class", "data"), -Fehler-Bodys behält und dekodiert und TimeoutError mit geschlossener Verbindung durchlässt; dass classify die Klasse jedes Codes von 100 bis 599 nennt; und dass report nur ein Timeout in eine Zeile verwandelt. Wenn die Lektionen 1 bis 5 bestanden wurden, stimmt das vielleicht schon.

Schreibe in tests/test_wire.py mindestens fünf Tests, die wire.get_json, wire.report oder wire.classify aufrufen und dabei fakes.scripted_response und fakes.fake_make_connection benutzen. Decke einen Erfolg ab, ein 4xx und ein 5xx mit JSON-Fehler-Bodys, ein Timeout (fake_make_connection(None, calls)) gefolgt von einer Prüfung, dass die Verbindung geschlossen wurde, und report bei einem 504 und bei einem Timeout. Schreibe einfache def test_...(): ohne . Behalte import wire und rufe die Funktionen in jedem Test darüber auf; füge keine Zeile from wire import hinzu und speichere sie nicht oben in der Datei unter eigenen Namen. Behalte die Tests, die die Datei schon hat.

Führe im python -m pytest -q aus.

Drücke dann Check my work. Die Prüfung ruft get_json, classify und report mit einer Ersatz-Verbindung und mit Status, Headern und Bodys auf, die du nicht gesehen hast. Dann führt sie deine Tests aus, zuerst gegen dein eigenes wire.py und dann gegen vier kaputte Clients: einen, der jedes 4xx einen Erfolg nennt, einen, der den Body jedes 4xx und 5xx wegwirft, einen, der sein eigenes Timeout als 504 meldet, und einen, der die Verbindung offen lässt, wenn keine Antwort ankommt. Deine Tests müssen gegen den ersten alle bestehen, und mindestens einer muss gegen jeden der anderen fehlschlagen. Ein Test, der nach dem Netzwerk greift, schlägt fehl, statt es zu erreichen. Nichts aus deinem Terminal wird bewertet.