Automaattestimine

Sissejuhatus

Tänapäevased projektid koosnevad mitmest osast. Need osad töötavad kooskõlas ja kui mingi osa teeb oma tööd valesti, siis võib olla terve süsteemi töö häiritud. Kui süsteemi täiendatakse või parandatakse, tuleb kindlaks teha, et ei tekkinud uusi vigu. Süsteemi töökindluse tagamiseks tuleb seda testida.

Üksuste testimine (Unit Testing)

Üksuste testimise puhul testitakse ühe konkreetset üksust koodist. Üksus võib olla nii funktsioon kui ka terve funktsioonidest (meetoditest) koosnev objekt. Objektide puhul testitakse seal olevaid meetodeid ja vaadatakse, et need õigesti mõjutaksid objekti. Funktsioonide puhul testitakse, et see annaks õige väljundi erinevate argumentide korral. Selleks, et programmeerija ei peaks kogu aeg funktsioone käivitama ja tulemust vaatama (näiteks print() lausetega), kasutatakse testimise tööriistu. Meie räägime kahest sellisest: unittest ja py.test.

Kuidas kirjutada teste

Testide kirjutamiseks tuleb eelkõige kindlaks teha, mida peab testitav funktsioon tegema ja mida see peab erinevate argumentide korral tagastama. Siis tuleb välja mõelda erinevaid argumentide kombinatsioone, mis võivad võimalikult palju vigu tuvastada.

Proovime välja mõelda natuke teste ühe funktsiooni jaoks. Olgu meil on funktsioon ruutvõrrandi (ax^2 + bx + c = 0) lahendamiseks. See võtab ette 3 argumenti (a, b, c) ja tagastab listi. Kui lahendusi ei ole, siis list peab olema tühi. Vastasel juhul listis on lahendid suvalises järjekorras.

def quadratic_solution(a, b, c):
        ...

See on kõik, mida testijal on vaja teada, et funktsiooni testida. Mida võib siin testida? Tuleb lihtsalt proovida erinevaid argumente ja kontrollida vastust.

  1. Erinevad a, b, c. (positiivsed, negatiivsed)
  2. Kui a/b/c on 0 või nad on kõik nullid.
  3. Kas on lahendid või ei ole.

Üksustestidega ei saa alati katta kõike võimalikke olukordi. On oluline, et kõige olulisemad olukorrad oleksid kaetud.

Testid kirjutatakse tavaliselt eraldi moodulisse (faili). See moodul koosneb funktsioonidest ja iga funktsioon testib ühte olukorda.

Testid peavad olema võimalikult lihtsad ja väga konkreetsed.

assert lause

assert lausega kontrollitakse alamprogrammi (näiteks funktsiooni) töötamise korrektsust. Kui väljund on õige, siis Pythoni interpretaator jätkab koodi käivitamist, ja kui ei ole õige, siis viskab AssertionError erindi.

assert [boolean expression]
# boolean expression is an expression that returns True/False.
# i.e. True, False, 5 > 3, 5 <= 3, a == b, z in [5, 6, 1]

assert -1 in quadratic_solution(5, 6, 1)
assert len(quadratic_solution(-8, 1, -10)) == 0
assert 45 > 107 # throws AssertionError, because 45 > 107 is always False

py.test

Seaded PyCharm’is:

File => Settings (Ctrl + Alt + S) => Tools => Project: [projekti nimi] => Project Interpreter => Install(roheline + märk) => Otsi “pytest” => Install Package

File => Settings (Ctrl + Alt + S) => Tools => Python Integrated Tools => Default Test runner: py.test

Testide kirjutamiseks tasub luua eraldi moodul, nt. ttumath_tests.py. py.test käivitab automaatselt vaid neid funktsioone, mille nime algus on “test”. Argumente testi funktsioonile ei määrata. Üksustesti kirjutamiseks tuleb luua funktsioon ja sinna panna mingi assert lause.

def test_base_arguments():
    assert -1 in quadratic_solution(5, 6, 1)

Funktsioonis võib olla rohkem kui 1 assert lause, aga see ei ole alati parim lahendus. Kui test ebaõnnestub ja see koosneb mitmest osast, siis me ei saa kergelt teada, mis osa ebaõnnestub. Parim variant on see, et üks funktsioon testib ühte olukorda.

from ttumath import quadratic_solution

def test_base_arguments():
    assert -1 in quadratic_solution(5, 6, 1)

def test_no_solutions():
    assert len(quadratic_solution(-8, 1, -10)) == 0

Kui palju teste kirjutada on testija valik. Kuid mida rohkem on teste ja mida keerukam on testitav funktsioon, seda rohkem aega testimine võtab.

Kui testid on valmis, siis on aeg need käivitada. Menüüs: Run (Alt+Shift+F10) => ‘py.test in ...’

Allpool ilmub aken, kus on info kõikidest testidest ja nende staatus (õnnestub/ei õnnestu).

Unittest

Seaded PyCharm’is: File => Settings (Ctrl + Alt + S) => Tools => Python Integrated Tools => Default Test runner: Unittests

Testide kirjutamiseks tasub luua eraldi moodul, nt. math_tests.py. Funktsiooni nimi peab algama sõnaga “test” ja ta peab olema klassis, mis pärib klassi unittest.TestCase. (Tuleb importida moodul unittest) Klassi nimi võib olla suvaline.

import unittest
from foos import small_calculator

class MathTests(unittest.TestCase):
    ...

Nüüd aga funktsioonidel peab olema argument self, mis laseb kasutada superklassi funktsioone.

import unittest
from foos import small_calculator

class MathTests(unittest.TestCase):
    def test_base_arguments(self):
        assert -1 in quadratic_solution(5, 6, 1)

    def test_no_solutions(self):
        assert len(quadratic_solution(-8, 1, -10)) == 0

Unittest’i juhul on kombeks kasutada tavalise assert asemel järgmiseid funktsioone:

assertEqual(first, second)      # if first != second, test fails
assertNotEqual(first, second)   # if first == second, test fails
assertTrue(expression)          # if expression == False, test fails
assertFalse(expression)         # if expression == True, test fails
...
Read more: https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertEqual

Et neid funktsioone kasutada, tuleb pöörduda argumendi self juurde.

import unittest
from foos import small_calculator

class MathTests(unittest.TestCase):
    def test_base_arguments(self):
        self.assertTrue(-1 in quadratic_solution(5, 6, 1))

    def test_no_solutions(self):
        self.assertEqual(0, len(quadratic_solution(-8, 1, -10)))

Käivitamine: Menüüs: Run (Alt+Shift+F10) => ‘Unittests for ...’

Kui test ebaõnnestub

Põhjused:

  1. Kas funktsioon annab vale tulemuse
  2. või test on valesti kirjutatu (nõuab valet vastust)

Esimene põhjus on programmeerija probleem, teine põhjus on testija probleem.

Edu!