Objectgerichte programma’s testen en refactoren · oefening
Bescherm gedrag met regressietests
Een regressie is gedrag dat eerst juist was en ongemerkt niet meer juist is. Een regressietest is de test die dat zou hebben ontdekt.
Ze komen op twee manieren in een programma terecht.
De eerste is een bugmelding. Er was iets mis, je begreep waarom en voordat je het herstelde schreef je een test die om precies die reden faalde. De test bewaakt nu de oplossing en faalt opnieuw op de dag dat iemand de fout terugbrengt.
De tweede is wat dit hoofdstuk nodig heeft. Je gaat code veranderen die nu werkt en je wilt weten of je die stukmaakt.
Schrijf op wat de code doet voordat je die verandert
De volgende les vindt een ontwerpprobleem in report.py. De les daarna herstelt het, wat betekent dat je een werkende functie herschrijft.
Bij het herschrijven van werkende code raakt gedrag zoek. Niet het gedrag waaraan je denkt, dat controleer je met de hand. Het andere soort: de lege quiz, de perfecte score, de exacte spaties in een regel die andermans code ontleedt.
Leg dus vast wat de code vandaag doet voordat je die aanraakt:
def test_the_full_report_for_the_chapter_quiz():
attempt = build_chapter_attempt()
lines = report_lines(attempt)
assert lines == ["Mina: 2 of 5", "Grade: F"]
Die test beweert niet dat de huidige uitvoer goed is. Die registreert alleen dat het programma dit nu oplevert. Dat is genoeg om refactoring veilig te maken. Daarom kun je de test schrijven voordat je weet hoe het nieuwe ontwerp eruitziet.
Tests met dit doel heten soms karakterisatietests, omdat ze beschrijven wat de code doet in plaats van te controleren wat die zou moeten doen.
Dek de gevallen af die je niet met de hand zou controleren
Het voor de hand liggende geval merk je op als het stukgaat. De grenzen gaan ongemerkt:
def test_an_empty_quiz_still_reports():
quiz = Quiz("Empty")
attempt = Attempt(quiz, "Mina")
lines = report_lines(attempt)
assert lines == ["Mina: 0 of 0", "Grade: F"]
Niets aan dit scenario is interessant totdat iemand report_lines herschrijft en de verhouding berekent voordat die controleert of er iets is om door te delen. Dan crasht het, in een geval dat niemand probeerde, voor een gebruiker met een lege quiz.
Les 5 testte dat percentage hiertegen beschermt. Deze test doet een andere bewering: dat de controle nog steeds bereikbaar is vanuit het rapport. Beide kunnen waar zijn, waarna één niet meer waar is, en alleen deze test merkt dat.
Je gaat een functie refactoren waarvan je de huidige uitvoer een beetje verkeerd vindt. Wat moet de regressietest controleren?
Leg niet meer vast dan je bedoelt
Een regressietest kan te ver gaan. Controleer je de exacte tekst van elke regel, dan heb je de formulering vastgezet. Een terechte verbetering aan het rapport ‘faalt’ nu en iemand moet bepalen of de test of de wijziging verkeerd is.
Daar is geen formule voor, alleen een nuttige vraag: als deze test ooit faalt, ben ik dan blij of geïrriteerd? Blij betekent dat die iets beschermde. Geïrriteerd betekent dat die iets bijkomstigs beschreef.
De twee regels van report_lines zijn het waard exact vast te leggen. Ze zijn wat het programma afdrukt en de volgende twee lessen gaan de code verplaatsen die ze bouwt.
Opdracht
Schrijf test_report_output.py en leg precies vast wat het rapport vandaag oplevert.
Importeer wat je nodig hebt uit quizapp.models en report en schrijf daarna drie tests:
test_the_full_report_for_the_chapter_quiz: Mina scoort 2 van de 5 punten en de volledige lijst is["Mina: 2 of 5", "Grade: F"].test_a_perfect_attempt_reports_an_a: Mina beantwoordt beide vragen juist en de lijst is["Mina: 5 of 5", "Grade: A"].test_an_empty_quiz_still_reports: een quiz zonder vragen levert["Mina: 0 of 0", "Grade: F"]op en gooit geen exceptie op.
Controleer de hele lijst in één keer, niet regel voor regel. Deze tests vormen het vangnet voor de refactoring in les 9 en moeten dus duidelijk falen als een deel van de uitvoer verandert.