Hoger Lager; toen mijn kameraad op school mij leerde programmeren, heeft hij mij IF's uitgelegd via het spelletje hoger-lager. Hoger Lager is dan ook het eerste spelleke da'k heb geschreven. Nu 20 jaar later (ik ben er 34) wil ik dit spel eens opnieuw schrijven maar dan ondersteund met moderne technieken zoals Test-Units.
20 jaar geleden begon ik met Basic, dit is mijn moderne Python-variant:
import random
getal = random.randint(0, 100) while True: invoer = int(input("Geef een getal van 0 tot 100: ")) if invoer == getal: print "Goed zo!" break elif invoer < getal: print "Neen! Hoger!" else: print "Neen! Lager:"
Na de eerste & tweede refactoring-sessies kwam ik tot deze versie (in het donkerrood gekleurd omdat de code maar tijdelijk is):
import random
def GenereerWillekeurigGetal(min, max): return random.randint(min, max)
def PrintVraagEnLeesGetalUit(min, max): return int(input("Geef een getal van %d tot %d: " % (min, max)))
def VerwerkEnVergelijkInvoer(IngevoerdGetal, TeRadenGetal): if IngevoerdGetal == TeRadenGetal: print "Goed zo!" return True elif IngevoerdGetal < TeRadenGetal: print "Neen! Hoger!" else: print "Neen! Lager:" return False
def SpeelHogerLager(): MinGetal = 0 MaxGetal = 100 TeRadenGetal = GenereerWillekeurigGetal(MinGetal, MaxGetal) while True: IngevoerdGetal = PrintVraagEnLeesGetalUit(MinGetal, MaxGetal) if VerwerkEnVergelijkInvoer(IngevoerdGetal, TeRadenGetal): break
def main(): SpeelHogerLager()
if __name__ == "__main__": main()
Volgens TDD (Test Driven Development) moeten we eerst een test schrijven en dan pas de code, maar goed, het spel Hoger Lager was er al. Dit is dus de testcode die ik geschreven heb om de bovenstaande spelcode te testen:
from HogerLager import * import unittest
class test_HogerLager(unittest.TestCase): def TestGenereerWillekeurigGetalMetMinMax(self, min, max): for j in range((max - min) * 10): getal = GenereerWillekeurigGetal(min, max) self.failIf(getal < min) self.failIf(getal > max) def test_GenereerWillekeurigGetal(self): for i in range(60): min = 50 - i max = 50 + i self.TestGenereerWillekeurigGetalMetMinMax(min, max) def test_VergelijkGetallen(self): self.assertFalse(VerwerkEnVergelijkInvoer(50, 49)) self.assertTrue (VerwerkEnVergelijkInvoer(50, 50)) self.assertFalse(VerwerkEnVergelijkInvoer(50, 51))
Het staat me echter niet aan dat de bovenstaande testcode ook het antwoord (Hoger of Lager) afdrukt. Dit gebeurt indirect door VerwerkEnVergelijkInvoer. Daarom heb ik VerwerkEnVergelijkInvoer gesplitst:
import random
def GenereerWillekeurigGetal(min, max): return random.randint(min, max)
def PrintVraagEnLeesGetalUit(min, max): return int(input("Geef een getal van %d tot %d: " % (min, max)))
def BerekenVerschil(IngevoerdGetal, TeRadenGetal): return IngevoerdGetal - TeRadenGetal
def VerwerkInvoer(IngevoerdGetal, TeRadenGetal, UitvoerActie): UitvoerActie(IngevoerdGetal, TeRadenGetal) return IngevoerdGetal == TeRadenGetal
def PrintResultaat(IngevoerdGetal, TeRadenGetal): Verschil = BerekenVerschil(IngevoerdGetal, TeRadenGetal) if Verschil == 0: print "Goed zo!" elif Verschil < 0: print "Neen! Hoger!" else: print "Neen! Lager:"
def VerwerkInvoerEnPrintResultaat(IngevoerdGetal, TeRadenGetal): return VerwerkInvoer(IngevoerdGetal, TeRadenGetal, PrintResultaat)
def SpeelHogerLager(): MinGetal = 0 MaxGetal = 100 TeRadenGetal = GenereerWillekeurigGetal(MinGetal, MaxGetal) while True: IngevoerdGetal = PrintVraagEnLeesGetalUit(MinGetal, MaxGetal) if VerwerkInvoerEnPrintResultaat(IngevoerdGetal, TeRadenGetal): break
def main(): SpeelHogerLager()
if __name__ == "__main__": main()
Tenslotte volgt de bijhorende testcode:
from HogerLager import * import unittest
def GeenUitvoerActie(a, b): pass
class test_HogerLager(unittest.TestCase): def TestGenereerWillekeurigGetalMetMinMax(self, min, max): for j in range((max - min) * 10): getal = GenereerWillekeurigGetal(min, max) self.failIf(getal < min) self.failIf(getal > max) def test_GenereerWillekeurigGetal(self): for i in range(60): min = 50 - i max = 50 + i self.TestGenereerWillekeurigGetalMetMinMax(min, max) def test_BerekenVerschil(self): self.failUnless(BerekenVerschil(50, 49) > 0) self.failUnless(BerekenVerschil(50, 50) == 0) self.failUnless(BerekenVerschil(50, 51) < 0) def test_VerwerkInvoer(self): self.assertFalse(VerwerkInvoer(50, 49, GeenUitvoerActie)) self.assertTrue (VerwerkInvoer(50, 50, GeenUitvoerActie)) self.assertFalse(VerwerkInvoer(50, 51, GeenUitvoerActie))
if __name__ == '__main__': unittest.main()
Eindnota: Ik ben nog niet overtuigd dat deze laatste versie van Hoger Lager de beste is, want de hoofdlus SpeelHogerLager is verantwoordelijk voor zowel het spel, als de invoer en de uitvoer via de console. Daardoor is het onmogelijk de hoofdlus te testen via een unittest-test_XXX-functie. Het is beter om de invoer en uitvoer via polyformisme te implementeren, de hoofdlus kan dan vervolgens via "mocking" getest worden. Voor het polyformisme zou ik in .NET een Interface definiƫren en gebruiken, in Python zal ik dit anders moeten oplossen (via een abstracte class). Ook de functie PrintVraagEnLeesGetalUit moet herbekeken worden want ze is zowat de enigste functie in het programma die een crash kan veroorzaken. Met name als een niet-cijfer ingevoerd wordt, zal de int-functieaanroep een exception triggeren. Deze "bug" bestaat ook in de eerste versie van het spel. Dit alles is voor een volgende blog.
05-07-2011, 00:00
Geschreven door Fibergeek 
|