Eén server, veel clients · oefening
Test met nepverbindingen
Binnenkomen, register en broadcast werken, en tot nu toe hebben alleen de demo en Submit dat laten zien. In deze les schrijf je eigen tests in tests/test_scoreboard.py en draai je ze in de
Een stand-in, afgekeken van de demo
Open scoreboard_demo.py en zoek demo_connection. Die bouwt een stand-in uit drie recv, send en close, die één state delen. send parset elk bericht en voegt het toe aan state["sent"], of werpt ConnectionClosedError op als de stand-in is gemaakt met fail_send=True. De drie functies worden daarna, met een id en een request, verzameld in een types.SimpleNamespace, een connection.send(...) en connection.id op aan, precies zoals hij dat bij een verbinding van de bibliotheek zou doen, en merkt het verschil nooit. In Python in Practice bouwde je stand-in-sessies op dezelfde manier.
De tests hebben minder nodig. add_viewer, apply_score en broadcast gebruiken alleen id en send, dus make_connection(conn_id, *, fail_send=False) kan klein zijn:
def make_connection(conn_id, *, fail_send=False):
sent = []
def send(text):
if fail_send:
raise closed_error()
sent.append(json.loads(text))
return types.SimpleNamespace(id=conn_id, send=send, sent=sent)
closed_error() staat meegeleverd bovenaan het bestand: hij bouwt de ConnectionClosedError die een send opwerpt als de verbinding van de kijker eindigde zonder close-sent op de stand-in te zetten, kan een test achteraf viewer.sent lezen.
Vier tests
Elke test volgt dezelfde drie stappen: zet een bord en een paar stand-ins klaar, handel via één aanroep van de server, en controleer wat de stand-ins vastlegden en wie er in board["viewers"] staat.
test_a_viewer_gets_the_board_when_it_joins: maak een bord, verander de versie en de punten van één team, roepadd_vieweraan, en controleer dat de stand-in precies die snapshot vastlegde en dat zijn id in het register staat.test_a_score_reaches_every_viewer: voeg twee kijkers toe, verwerk één score, en controleer dat beide de nieuwe versie en punten ontvingen.test_a_failed_viewer_is_dropped_and_the_rest_still_hear: registreer een kijker die is gemaakt metfail_send=Truevóór een werkende, verwerk een score, en controleer dat de mislukte het register heeft verlaten en dat de werkende de update toch ontving. De volgorde doet ertoe: een mislukte kijker die als laatste geregistreerd is, kan niet laten zien of dena hem doorging. test_parse_message_refuses: houd de regel@pytest.mark.parametrize, die je in hoofdstuk 3 tegenkwam, en geef hem minstens drie teksten dieparse_messagemoet weigeren. Het geval mettrueals punten staat er al. Roep binneninparse_messageaan in eentry; keer terug vanuitexcept ValueError:, en schrijf na hettry-statementassert False, ...met de tekst, zodat een tekst die wordt geaccepteerd de test laat falen.
Om een stand-in zonder snapshot bij binnenkomst in het register te zetten, wijs je hem direct toe: board["viewers"]["broken"] = broken.
Welke versie van de server moet test_a_failed_viewer_is_dropped_and_the_rest_still_hear betrappen?
Waarom via de module
Het bestand importeert de import scoreboard_server en import scoreboard_messages, en de tests roepen scoreboard_server.apply_score(...) aan. Houd dat zo. Check my work draait je tests tegen versies van de server die elk één fout bevatten, door de functie in de module te verwisselen terwijl je tests draaien, om te zien of je tests het merken. Een aanroep via de module zoekt de functie op op het moment van de aanroep, dus die krijgt de verwisselde. Een naam die met from scoreboard_server import apply_score is geïmporteerd, werd één keer gebonden, toen het bestand werd geïmporteerd, en houdt de oude functie. Om dezelfde reden doe je elke aanroep binnen een testfunctie, niet bovenaan het bestand.
Waarom roepen de tests scoreboard_server.apply_score(...) aan in plaats van apply_score met from te importeren?
Draai ze
Deze les gebruikt de terminal. Draai de tests vanuit de projectmap:
python -m pytest -q
Elke placeholder faalt totdat je hem vervangt. Op de site meldde het afgemaakte bestand 8 passed: drie tests, en vijf teksten in de geparametriseerde, die elk als aparte test worden geteld. De demo draait nog zoals eerst, en zijn uitvoer is sinds les 4 niet veranderd:
python scoreboard_demo.py
Deze tests bewijzen wat één thread ziet: de snapshot bij binnenkomst, de broadcast en het verwijderen van een verdwenen kijker. Ze bewijzen niet de race bij de deur uit les 3, want die heeft twee threads op precies het verkeerde moment nodig. Die werd bewezen door een eigen instrument van de controle toen je in les 3 op Submit klikte; je tests hoeven dat niet.
Als elke test slaagt, klik je op Check my work. De volgende les schrijft de handler van de scorebijhouder, het laatste stuk van scorebord v1, en draait hem met meerdere kijkers op
Opdracht
Schrijf in tests/test_scoreboard.py make_connection en vervang de vier placeholders door tests. Laat closed_error en de imports zoals ze zijn.
make_connection(conn_id, *, fail_send=False): geef eentypes.SimpleNamespaceterug metid, eensenddiejson.loads(text)aan eentoevoegt (of closed_error()opwerpt alsfail_sendwaar is), en die lijst alssent.test_a_viewer_gets_the_board_when_it_joins: nascoreboard_server.add_viewerheeft de stand-in de snapshot van het bord vastgelegd, met zijn versie en scores, en staat zijn id inboard["viewers"].test_a_score_reaches_every_viewer: twee kijkers, éénscoreboard_server.apply_score, en beide ontvingen de nieuwe snapshot.test_a_failed_viewer_is_dropped_and_the_rest_still_hear: een falende kijker, geregistreerd vóór een werkende; na een score is de falende weg uitboard["viewers"]en heeft de werkende de update ontvangen.test_parse_message_refuses: geparametriseerd met minstens drie teksten, waaronder het geval mettrueals punten, diescoreboard_messages.parse_messageelk metmoet weigeren.
Roep elke serverfunctie binnen de tests aan via scoreboard_server. en scoreboard_messages., nooit met from ... import. Gebruik alleen stand-ins: geen
Draai python -m pytest -q in de add_viewer die registreert zonder de snapshot te sturen, een broadcast die stopt bij de eerste kijker die weg is, een broadcast die die kijker houdt, en een is_whole_number die True accepteert. Tegen elk ervan moet minstens één test falen. Een run tegen het werkende scorebord die langer dan 1 seconde duurt, wordt gestopt. De controle leest je terminal niet.