Riippuvuuksien injektointi
Riippuvuuksien injektointi
Lue ensin http://jamesshore.com/Blog/Dependency-Injection-Demystified.html
Alla oleva koodi löytyy poetry-muotoisena projektina kurssin tehtävärepositoriosta hakemistosta koodi/viikko1/riippuvuuksien-injektointi-1
Seuraavassa yksinkertainen laskin:
class Laskin:
def suorita(self):
while True:
luku1 = int(input("Luku 1:"))
if luku1 == -9999:
return
luku2 = int(input("Luku 2:"))
if luku2 == -9999:
return
vastaus = self._laske_summa(luku1, luku2)
print(f"Summa: {vastaus}")
def _laske_summa(self, luku1, luku2):
return luku1 + luku2
def main():
laskin = Laskin()
laskin.suorita()
if __name__ == "__main__":
main()
Ohjelman ikävä puoli on se, että Laskin
-luokalla on konkreettinen riippuvuus tulostuksen ja syötteen luvun hoitaviin funktioihin print
input
.
Konkreettiset riippuvuudet vaikeuttavat testaamista ja tekevät ohjelman laajentamisen vaikeaksi.
Riippuvuuden eliminointi
Eristetään tulostuksen ja syötteen lukeminen omaan, luokan KonsoliIO
olioon:
class KonsoliIO:
def lue(self, teksti):
return input(teksti)
def kirjoita(self, teksti):
print(teksti)
Muokataan luokkaa Laskin
siten, että se saa konstruktorin parametrina olion, jonka kautta se hoitaa käyttäjän kanssa tapahtuvan kommunikoinnin:
class Laskin:
def __init__(self, io):
self._io = io
def suorita(self):
while True:
luku1 = int(self._io.lue("Luku 1:"))
if luku1 == -9999:
return
luku2 = int(self._io.lue("Luku 2:"))
if luku2 == -9999:
return
vastaus = self._laske_summa(luku1, luku2)
self._io.kirjoita(f"Summa: {vastaus}")
def _laske_summa(self, luku1, luku2):
return luku1 + luku2
Sovellus käynnistetään nyt siten, että sille injektoidaan kommunikaation hoitava olio konstruktorin parametrina:
def main():
io = KonsoliIO()
laskin = Laskin(io)
laskin.suorita()
main()
Testaus
Ohjelmalle on nyt helppo tehdä yksikkötestit. Testejä varten toteutetaan valeluokka eli “stubi”, joka toimii ulkoisesti samalla tavalla kuin luokan KonsoliIO
oliot:
class StubIO:
def __init__(self, inputs):
self.inputs = inputs
self.outputs = []
def lue(self, teksti):
return self.inputs.pop(0)
def kirjoita(self, teksti):
self.outputs.append(teksti)
Stubille voidaan siis antaa “käyttäjän syötteet” konstruktorin parametrina. Ohjelman tulosteet saadaan suorituksen jälkeen kysyttyä stubilta.
Testi seuraavassa:
class TestLaskin(unittest.TestCase):
def test_yksi_summa_oikein(self):
io = StubIO(["1", "3", "-9999"])
laskin = Laskin(io)
laskin.suorita()
self.assertEqual(io.outputs[0], "Summa: 4")
Yhteenveto
Riippuvuuksien injektointi on siis oikeastaan äärimmäisen simppeli tekniikka, moni on varmaan sitä käyttänytkin jo ohjelmoinnin peruskursseilla.
Jos ajatellaan vaikkapa tietokonepelejä, joiden toiminta riippuu usein satunnaisluvuista. Jos peli on koodattu seuraavasti, on automatisoitu testaus erittäin vaikeaa:
class Peli:
def liikuva_pelaajaa(self):
suunta = random.randint(0, 8)
Jos taas satunnaislukugeneraattori injektoidaan pelille seuraavasti
class Peli:
def __init__(self, arpa):
self._arpa = arpa
def liikuva_pelaajaa(self):
suunta = self._arpa.randint(0, 8)
voidaan testatessa injektoida pelille versio satunnaisgeneraattorista, jonka arpomia lukuja voidaan kontrolloida testeistä käsin. Esimerkiksi seuraavassa sellainen versio satunnaislukugeneraattorista, joka palauttaa aina luvun 1 kutsuttaessa metodia randint:
class Arpa:
def randint(self, a, b):
return 1