Eindproject: de leeslijstservice en -client · eindproject
Eindproject: Bewijs het met tests
Client en server zijn het eens over alle elf opdrachten. Dat bewijst dat de code vandaag werkt. “Af” vraagt nog één ding: als iemand volgende maand één regel veranderde en een rij van het contract brak, zou een test dat merken. De tests die met het project meekwamen, dekken de rijen uit hoofdstuk 4 tot en met 10, en geen ervan kijkt naar de drie rijen die je in dit hoofdstuk hebt hersteld en gebouwd. Deze les voegt die tests toe.
Wat Check my work doet
Check my work beoordeelt deze les in twee delen.
Eerst herhaalt hij elke controle van les 1 tot en met 3, elk als eigen controle, en kijkt hij of SERVICE_CONTRACT.md en README.md nog in het project staan en of geen enkel bestand in tests/ socket, http.server, http.client, iets uit urllib, requests of httpx importeert, of practice-api noemt. Een test die een van de exceptionklassen van requests nodig heeft, bereikt die via de client, als reading_client.requests.ConnectTimeout, zoals test_no_answer_exits_1 al doet.
Dan voert hij je tests uit met pytest, heel tests/: één keer tegen je code zoals die is, en één keer voor elk van zes versies van je code waarin één
handleantwoordt op/v0/books/{id}met een 308 waarvan delocation/booksis, zonder het id;cached_showvraagt een verlopen item op zonderIf-None-Match;maingeeft 0 terug als de server antwoordde met een status 4xx of 5xx;callstuurt zijn verzoek zonder timeout;handlestuurt de hele body mee met zijn 304;create_bookantwoordt 201 zonderlocation.
Je tests moeten de eerste keer allemaal slagen, en elke andere keer moet er minstens één mislukken. Twee van de zes worden gevangen door tests die je al hebt: test_an_error_status_exits_2_with_one_line mislukt bij nummer 3, en test_call_sends_one_request_with_a_timeout bij nummer 4. De andere vier hebben nieuwe tests nodig.
Tijdens elke run zorgt Check my work ervoor dat geen test een netwerk bereikt of een reading_client.requests kan zijn main aanroept zonder session mislukt met een melding die zegt dat hij de sessie moet gebruiken die hij krijgt. reading_server.http kan geen poort binden. Elke versie wordt via haar reading_server.handle(...) en reading_client.main(...) aan zoals de bestanden al doen: een regel from reading_client import main, of een functie die bovenaan een bestand in een naam wordt bewaard, houdt het origineel vast en verbergt de wissel.
Elke run stopt bij zijn eerste mislukte test, en een run die na 3 seconden nog bezig is, wordt gestopt. Na in totaal 8 seconden begint er geen nieuwe run meer, en de melding noemt de versies die al geprobeerd zijn.
Je tests slagen. Check my work wisselt een cached_show in die nooit If-None-Match stuurt, en je tests slagen nog steeds. Wat betekent dat?
Zes tests
Schrijf minstens één test voor elke helft van elke rij in “Not built yet”. Elke naam werkt, en pytest vindt elke functie waarvan de naam begint met test_. Het project heeft in totaal minstens 15 tests nodig: de 9 die meekwamen en minstens zes nieuwe. Het plan hieronder heeft er zeven, omdat hervalidatie er twee waard is.
De serverhelften komen in tests/test_server.py, via handle en de eigen make_request(method, target, data=None, headers=None) van het bestand, zoals hoofdstuk 5 je leerde. Hier is de eerste, voor het oude pad:
def test_legacy_path_keeps_the_id_and_query():
"""Any method on /v0/books/2?x=1 gets a 308 to /books/2?x=1 and changes nothing."""
state = reading_server.sample_state()
for method in ["GET", "PUT", "DELETE"]:
response = reading_server.handle(make_request(method, "/v0/books/2?x=1", None, WRITER), state)
assert response["status"] == 308
assert response["headers"]["location"] == "/books/2?x=1"
assert response["body"] == b""
assert state == reading_server.sample_state()
De 304-test vraagt /books/1 één keer op, neemt de etag uit dat antwoord en stuurt die terug in If-None-Match: het tweede antwoord moet een 304 zijn met dezelfde tag en de body b"". De location-test post een boek met het token van de schrijver en vergelijkt de location van de 201 met /books/3, het pad van het derde boek.
De clienthelften komen in tests/test_client.py, met fakes.fake_session(replies, calls): die beantwoordt elk verzoek met het volgende antwoord en voegt per aanroep een calls, dus calls[1]["headers"] is wat het tweede verzoek stuurde. fakes.fake_response(status, data, headers=..., url=..., history=...) bouwt een antwoord, en de headers daarvan zijn niet hoofdlettergevoelig, net als die van requests. De eigen run_main(argv, session, tmp_path, now=0) van het bestand voert main uit met een cachebestand binnen tmp_path, de map die pytest voor elke test maakt, zoals in Python in Practice.
Hervalidatie heeft twee tests nodig.
cached_showstuurtIf-None-Matchin deheaders=van het verzoek, nooit op de sessie, dus daar kijkt je test. De ene toont hetzelfde boek op 0 en 59 met een antwoord datmax-age=60zegt, en controleert dat er één verzoek was en twee afgedrukte regels. De andere slaat het boek op bij 0, toont het opnieuw op 60 met een 304 als tweede antwoord, en controleert datcalls[1]["headers"]["if-none-match"]de opgeslagen tag is.Nieuwe boeken: een
addwaarvan het antwoordurl="http://shelf.test:8123/books"enlocation: /books/3heeft, moet... at http://shelf.test:8123/books/3afdrukken.Oude paden: een
updatewaarvan het antwoord een 308 inhistoryheeft, moetfollowed 308 fromen devan die 308 afdrukken op standard error, en toch 0 teruggeven. De capsys-fixture van pytest vangt beide streams op:capsys.readouterr().err.
Een fake sessie zonder antwoorden meer werpt meteen een fout op in plaats van te wachten.
Je opdracht
Voeg de drie servertests toe aan
tests/test_server.pyen de vier clienttests aantests/test_client.py.Voer de testsuite uit tot hij slaagt:
python -m pytest -q
echo $?
De eigen exitstatus van pytest is 0 als elke test slaagde en 1 als er een mislukte, dus echo $? drukt 0 af zodra de suite groen is.
Klik op Check my work.
Als deel één mislukt, noemt de melding de regel uit les 1 tot en met 3. Als deel twee zegt dat je tests slaagden tegen een foute versie, noemt hij de fout en het soort test dat die zou merken. Vraag Monty om die melding samen met je naast je testbestand te lezen, zonder de test te schrijven.
Als elke controle slaagt, is het project af: een server en een client die aan één contract voldoen, en tests die zes realistische manieren om het te breken zouden vangen. De volgende les haalt het van de site.
Opdracht
Bewijs het eindproject met tests die een gebroken rij zouden merken.
Voeg minstens zes tests toe, één of meer voor elke helft van elke rij in “Not built yet”: in tests/test_server.py, via reading_server.handle en make_request, de 308 voor een /v0/books/{id}-pad met zijn exacte location, de 304 zonder body voor een overeenkomende If-None-Match, en de location van de 201; in tests/test_client.py, via fakes.fake_session en run_main met tmp_path, een vers item dat niets verstuurt, een verlopen item dat met zijn opgeslagen tag wordt gevraagd, de absolute add, en een followed-regel op standard error. Geparametriseerde tests zijn prima. Houd de 9 tests die met het project meekwamen, zodat pytest er minstens 15 verzamelt.
Elke test roept de from reading_server import of from reading_client import, en geen naam op moduleniveau die handle, create_book, cached_show, main of call vasthoudt), geeft een sessie mee aan main, en importeert niets dat een netwerk bereikt: geen socket, http.server, http.client, urllib, requests of httpx. Houd elke server- en clientrij uit les 1 tot en met 3. Voer python -m pytest -q uit in de
Klik daarna op Check my work. Deel één herhaalt elke controle van les 1 tot en met 3, en controleert dat SERVICE_CONTRACT.md en README.md er zijn en wat de bestanden in tests/ importeren. Deel twee voert je tests uit met pytest, terwijl reading_client.requests niets kan versturen en reading_server.http geen handle dat een 308 stuurt waarvan de location het id weglaat; cached_show dat een verlopen item vraagt zonder If-None-Match; main dat 0 teruggeeft bij een foutstatus; call zonder timeout; handle dat een body meestuurt met zijn 304; create_book zonder location. Elke keer moet een test mislukken. Elke run stopt bij zijn eerste mislukking; een run die na 3 seconden nog bezig is, wordt gestopt, en na in totaal 8 seconden begint er geen nieuwe run meer. Hij leest je terminal niet.