requests en httpx, nu je het weet · oefening
Test een client zonder server
Elke controle van who_am_i en trail_httpx tot nu toe had een server nodig: pl_http startte de jouwe, en de bibliotheken praatten ermee. Tests horen dat niet te doen. In Networks with Python testte je socketcode door die via een geïnjecteerde connect een vervangende
Een transport is de naad (seam)
Een httpx-client doet zijn httpx.MockTransport is een transport dat in plaats daarvan een functie van jou aanroept:
import httpx
def answer(request):
if request.url.path == "/v0/books/2":
return httpx.Response(308, headers={"location": "/books/2"})
return httpx.Response(200, json={"id": 2, "title": "Café Society", "author": "Ana Ruiz"})
client = httpx.Client(transport=httpx.MockTransport(answer))
De functie, vaak een handler genoemd, krijgt een httpx.Request en geeft een httpx.Response terug. Het verzoek bevat alles wat de client zou hebben verstuurd: request.method, request.url.path, request.headers (met host er al in) en request.content, de body als bytes. httpx.Response(200, json=...) codeert de content-type: application/json voor je toe.
Alles boven het transport draait zoals gewoonlijk. Wanneer answer de 308 teruggeeft en de aanroep follow_redirects=True meegaf, lost de client de location op, bouwt het volgende verzoek en roept answer opnieuw aan met pad /books/2. Op geen enkel moment wordt er een socket geopend; de handler is de hele server.
Een functie aan een andere functie meegeven is iets wat je doet sinds de geïnjecteerde connect in Networks with Python. Hier is de handler de MockTransport roept hem aan.
Wat krijgt een handler van MockTransport binnen en wat geeft hij terug?
De handler kan ook de time-out zien. httpx hangt die aan elk verzoek als request.extensions["timeout"], een HTTPX_TIMEOUT van de cursus, httpx.Timeout(2.0, connect=1.0), is dat {'connect': 1.0, 'read': 2.0, 'write': 2.0, 'pool': 2.0}; zonder opgegeven time-out is elke fase de 5.0 van httpx. Een handler die elk verzoek, of de delen waar hij om geeft, aan een
Hoe kan een test zien welke time-out een aanroep van httpx gebruikte?
De vervangende sessie die je kent
requests heeft ook transports, maar who_am_i neemt een hele sessie, dus de vervanger kan een hele sessie zijn. Open fakes.py. fake_session(replies, calls) geeft een request, get, post, put en delete, de who_am_i aanroept. Elke aanroep voegt een dict toe aan je lijst calls, met elk keyword dat requests zou hebben ontvangen: method, url, params, headers, json, auth, timeout, allow_redirects. Daarna antwoordt hij met het volgende item van replies, of werpt het op als dat item een
fake_response(status, data) bouwt de antwoorden. Het heeft status_code, json(), headers die hoofdletterongevoelig zijn, en een raise_for_status() die requests.HTTPError opwerpt bij 4xx en 5xx, dus who_am_i draait er ongewijzigd tegen:
calls = []
replies = [fakes.fake_response(200, {"user": "ana"}), fakes.fake_response(200, {"user": "ana"})]
session = fakes.fake_session(replies, calls)
Nadat who_am_i gedraaid heeft, is calls[0] de login en calls[1] de /me. Een test kan nu met een assert controleren wat geen server je rechtstreeks zou laten zien: de inloggegevens die in auth zijn meegegeven, en de time-out van elke aanroep.
Test via de module
Je tests worden ook gecontroleerd. Check my work draait ze tegen je eigen side_by_side.py, en daarna opnieuw met één functie vervangen door een versie met een fout, om te zien of een test dat merkt. Hij zet elke versie op haar plek door haar op de side_by_side.trail_httpx = .... Een test die side_by_side.trail_httpx(...) aanroept, zoekt de naam op op het moment dat hij draait, dus hij treft de versie die er op dat moment staat. Een regel from side_by_side import trail_httpx kopieert het origineel naar een eigen naam in je testbestand, en de verwisseling bereikt die nooit.
De versies met fouten zijn de versies waarnaar de twee tests van deze les vernoemd zijn: een trail_httpx die de doorverwijzing niet volgt, en een who_am_i waarvan de GET /me geen time-out meegeeft. Een test die alleen de time-out van de loginaanroep controleert, slaagt bij de tweede, dus controleer beide aanroepen.
Waarom roep je side_by_side.trail_httpx(...) aan in plaats van de naam te importeren met from side_by_side import trail_httpx?
Je opdracht
Beide functies nemen hun vervanger na de * in hun definitie, dus een test geeft hem bij naam mee, als client=client of session=session:
def trail_httpx(url, *, client=None, timeout=HTTPX_TIMEOUT):
...
def who_am_i(base_url, user, password, *, session, timeout=TIMEOUT):
...
Vervang in tests/test_side_by_side.py de twee plaatshouders die assert False, "not written yet" zeggen:
test_trail_httpx_follows_a_redirect: schrijf een handler die voor het ene pad 308 met eenlocationantwoordt en voor het volgende 200, maak een client methttpx.MockTransport(handler), roepside_by_side.trail_httpx(url, client=client)aan, en controleer met een assert dat het spoor beide regels bevat, de 308 eerst.test_who_am_i_sends_credentials_and_timeouts: geefside_by_side.who_am_ieenfakes.fake_sessionmet twee antwoorden en een time-out naar keuze, en controleer daarna met asserts de, de authvan de loginaanroep en detimeoutvan beide aanroepen.
Namen en extra tests zijn aan jou. Elke test moet zijn eigen vervanger aan de functie meegeven; een test die client of session weglaat, zou een netwerk nodig hebben, en faalt hier. Draai alleen dit bestand, want tests/test_client.py wacht op les 6:
python -m pytest -q tests/test_side_by_side.py
Het zou 2 passed moeten melden, of meer als je er meer schreef. Klik daarna op Check my work. Les 5 bekijkt de regels die beide bibliotheken versturen zonder dat iemand erom vroeg.
Opdracht
Test trail_httpx en who_am_i zonder server.
Vervang in tests/test_side_by_side.py de twee plaatshouders:
test_trail_httpx_follows_a_redirect: een handlerfunctie antwoordt voor het ene pad 308 met eenlocationen 200 voor het pad waar die naartoe wijst. Maak een client methttpx.Client(transport=httpx.MockTransport(handler)), roepside_by_side.trail_httpx(url, client=client)aan, en controleer met een assert dat het spoor beide regels bevat, de 308 eerst.test_who_am_i_sends_credentials_and_timeouts: roepside_by_side.who_am_i(...)aan metfakes.fake_session(replies, calls)en een time-out naar keuze. Controleer met asserts wat hij teruggeeft, deauthvan de loginaanroep en detimeoutvan beide vastgelegde aanroepen, deGET /menet zo goed als dePOST /login.
Houd import side_by_side en roep de from side_by_side import toe en bewaar geen van beide functies in een naam bovenaan het bestand. Geef bij elke aanroep een client of een sessie mee. Importeer geen modules socket, http of urllib. Meer tests zijn welkom.
In de python -m pytest -q tests/test_side_by_side.py deze tests; als beide geschreven zijn, meldt het 2 passed of meer.
Klik daarna op Check my work. Die leest het bestand en draait het daarna drie keer met pytest: tegen je eigen side_by_side.py, tegen een trail_httpx die geen who_am_i waarvan de GET /me geen time-out heeft. Je tests moeten allemaal slagen tegen de eerste, en tegen elk van de andere moet er minstens één falen. Terwijl ze draaien, weigeren requests en httpx van side_by_side iets te versturen, dus een test die zijn vervanger vergeet, faalt. Een run die langer dan 3 seconden duurt, wordt gestopt en gemeld. Niets uit je terminal wordt beoordeeld.