Slotproject: een uitleenbibliotheek voor de buurt · oefening
Test, refactor en beoordeel het systeem
Het systeem is af. Elke bewerking uit het contract werkt en de verborgen controles hebben dat bij elke stap bevestigd.
Dat is niet hetzelfde als zelf weten dat het werkt.
Tests die je niet zelf schreef, bewijzen minder dan je denkt
Elke les tot nu toe beoordeelde je code met controles die iemand anders schreef. Dat is nuttig, maar heeft een specifieke beperking: ze vertellen of je aan een specificatie voldoet. Ze vertellen niet of jij begrijpt wat je hebt gebouwd.
Hoofdstuk 13 pleitte voor je eigen tests. Hier schrijf je die voor een systeem dat je zelf hebt ontworpen. Het verschil is dat niemand je heeft verteld welke gevallen ertoe doen. Die kiezen is het werk.
Waar je zoekt
Een contract van deze omvang bevat meer toetsbare beweringen dan iemand allemaal zou testen. Drie vragen helpen je kiezen.
Wat gaat ongemerkt mis? De leenlimiet wordt gecontroleerd door te tellen. Als die telling onjuist was, zou er niets crashen: een lid zou gewoon blijvend één item te veel mogen lenen, zonder dat iemand het merkte.
Wat heeft een grens? Te laat is >, niet >=. Les 5 van hoofdstuk 13 betoogde dat een geval aan elke kant van een grens meer waard is dan tien gevallen midden in het bereik.
Wat deed je tijdens het schrijven verkeerd? Dat is de eerlijkste bron voor een test. Als je voor iets twee pogingen nodig had, hoort de test die dat had ontdekt in het bestand.
Minstens één foutpad
Het contract vraagt hier expliciet om. Het is de helft die mensen overslaan.
Testen dat uitlenen werkt, is eenvoudig en geruststellend. Testen dat een geweigerde uitlening om de juiste reden wordt geweigerd en niets achterlaat, vertelt je of de vroege controles uit les 5 in de juiste volgorde staan:
from pythonland_test import raises
def test_a_second_borrower_is_refused():
shelf = build_library()
shelf.checkout("b1", "m1", 10)
with raises(ItemUnavailableError) as caught:
shelf.checkout("b1", "m2", 11)
assert caught.value.item_id == "b1"
In les 8 kreeg die exceptie attributen zodat een test iets precies kon vaststellen. Een test die alleen controleert of “er een fout optrad” zou bij alle vier de weigeringen slagen. Dat is een andere manier om te zeggen dat hij bijna niets controleert.
Waarom schrijf je je eigen tests als de verborgen controles al slagen?
Refactor met de tests als bescherming
Zodra de tests groen zijn, bekijk je library.py met de vrijheid die hoofdstuk 13 beschreef: de interne implementatie mag veranderen terwijl het publieke contract gelijk blijft. Tests leveren bewijs, maar kunnen gedrag missen dat het contract nog steeds vereist.
Veelvoorkomende kandidaten, al kan het antwoord eerlijk gezegd ook “niets” zijn:
Een methode die meerdere onafhankelijke beslissingen neemt, kan duidelijker worden als je er één onderbrengt in een gerichte hulpmethode. Witregels alleen zijn geen reden om haar op te splitsen.
Een commentaarregel die uitlegt wat een blok doet, is vaak een toekomstige methodenaam.
Herhaalde opzoekbewerkingen zoals
self._items[item_id]in meerdere methoden kunnen baat hebben bij een kleine private hulpmethode.
Als je iets verandert, voer de tests dan na elke stap uit in plaats van alleen aan het eind. Dat is de hele werkwijze en daarom kwamen de tests eerst.
Wat je nu bouwt
Het bestand in de editor is tests/test_library.py. Schrijf minstens vijf tests: het normale pad, een weigering gecontroleerd op type en attributen, een grensgeval voor te late uitleningen en verder alles waarvan je over een halfjaar nog wilt weten of het werkt.
Gebruik Run tests tussendoor. Klik op Submit wanneer ze slagen.
Opdracht
Schrijf je eigen tests voor het systeem in tests/test_library.py.
Minstens vijf testfuncties die ten minste het volgende dekken:
een lid leent een item en het item verdwijnt uit
available_items();een tweede lener wordt geweigerd voor een al uitgeleend item met
ItemUnavailableError, gecontroleerd op zowel het type als het attribuutitem_idofdue_day;de grens voor te late uitleningen, op de uiterste inleverdag en de dag erna;
een teruggave waarna het item weer beschikbaar wordt.
Importeer raises uit pythonland_test voor de weigering. Elke test richt zijn eigen bibliotheek in, zodat geen test afhankelijk is van een eerder uitgevoerde test.
Lees daarna library.py opnieuw en refactor alles wat je netter zou willen. Voer na elke wijziging Run tests uit. Als niets hoeft te veranderen, zeg dat dan en laat het met rust: een refactoring die niemand nodig heeft, is geen verbetering.