Riippuvuuksien injektointi
(Jos teet jo harjoitustyötä) Lue ensin moodle
Kurssin Moodle sivujen sisältämä tieto kannattaa lukea aina kurssin aluksi! Siellä selitetään kurssin tavoitteista, arvostelusta ja rakenteesta tarkemmin. Moodlesta löytyy myös suorat linkit näiden sivujen olennaisimpiin osiin. Nämä sivut olettavat, että tunnet moodlessa olevan materiaalin ja voivat tuntua sekavilta jos et.
Nämä ohjeet on kopioitu kurssin Ohjelmistotuotanto materiaalista
Lue ensin http://jamesshore.com/Blog/Dependency-Injection-Demystified.html
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.
Suoran 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
Korjauksia tälle sivulle
Tee korjausehdotus editoimalla tätä tiedostoa GitHubissa.