Objectgerichte programma’s testen en refactoren · oefening
Test het aanmaken en toestandswijzigingen
Question en Quiz waren eenvoudig te testen, omdat twee keer iets vragen beide keren hetzelfde antwoord geeft.
Attempt is anders. Die heeft een score die verandert. Een veranderend object testen betekent twee afzonderlijke dingen testen: hoe het is bij het ontstaan en wat elke methode ermee doet.
Test de begintoestand
Hoofdstuk 5 besteedde een heel hoofdstuk aan het idee dat een constructor een object hoort op te leveren dat al geldig is. Die bewering is testbaar en het is de eerste test die je schrijft:
def test_a_new_attempt_has_not_scored():
quiz = Quiz("Review")
attempt = Attempt(quiz, "Mina")
assert attempt.score == 0
Deze lijkt bijna te klein om moeite voor te doen. Dat is niet zo. Het is de test die faalt op de dag dat iemand _earned een andere beginwaarde geeft of vergeet die in te stellen. De test faalt meteen in plaats van drie methoden later.
Let op de opzet: het object aanmaken is hier de act. Daardoor is het een constructietest.
Test een wijziging als vóór en na
Voor een methode die toestand verandert, gaat de assertie niet over een waarde. Die gaat over een verschil.
def test_a_correct_answer_adds_the_points():
quiz = Quiz("Review")
question = Question("Which keyword starts a function?", "def", 2)
quiz.add(question)
attempt = Attempt(quiz, "Mina")
attempt.answer(question, "def")
assert attempt.score == 2
answer() geeft niets terug. Er is geen teruggegeven waarde om te controleren en dat is normaal voor een methode die het object moet veranderen. In plaats daarvan controleer je het object achteraf.
Test dat er niets gebeurde
De andere helft van een toestandswijziging is het geval waarin de toestand niet hoort te veranderen:
def test_a_wrong_answer_scores_nothing():
...
attempt.answer(question, "class")
assert attempt.score == 0
Deze tests voelen tijdens het schrijven overbodig en verdienen later hun plek. ‘Telt de punten op als het antwoord juist is’ en ‘telt niets op als het onjuist is’ zijn twee verschillende beloften. Code kan de eerste makkelijk nakomen en de tweede breken. Een methode die altijd de punten optelt slaagt perfect voor de eerste test.
Waarom is test_a_wrong_answer_scores_nothing belangrijk als er al een test voor het juiste antwoord is?
Wanneer de reeks het gedrag is
De vorige les zei één act per test, met één terechte uitzondering. Hier is die:
def test_answering_several_questions_accumulates():
...
attempt.answer(first, "def")
attempt.answer(second, "class")
assert attempt.score == 5
Twee aanroepen, één act. Het geteste gedrag is niet ‘een vraag beantwoorden’, maar ‘vragen beantwoorden bouwt een totaal op’. Dat kun je niet met één aanroep uitdrukken. De naam zegt dat ook, zodat een lezer niet wordt verrast.
De grens is of de aanroepen één idee vormen. Twee aanroepen die optellen zijn één idee. Een juist antwoord gevolgd door een onjuist antwoord kan ook één nuttige reeks vormen: die controleert of een onjuist antwoord eerder verdiende punten behoudt. Splits een test als die onafhankelijke gedragingen combineert, niet alleen omdat die twee aanroepen bevat.
Opdracht
Schrijf test_attempt.py voor hoe een Attempt begint en verandert.
Importeer Attempt, Question en Quiz uit quizapp.models en schrijf daarna vier tests:
test_a_new_attempt_has_not_scored: een nieuwe poging heeft score 0.test_a_correct_answer_adds_the_points: een vraag van 2 punten juist beantwoorden maakt de score 2.test_a_wrong_answer_scores_nothing: dezelfde vraag onjuist beantwoorden laat de score op 0.test_answering_several_questions_accumulates: een vraag van 2 punten en een vraag van 3 punten juist beantwoorden maakt de score 5.
Elke vraag die je beantwoordt moet eerst in de quiz staan. Controleer score met asserties, nooit _earned.