Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

3. diel - Testovanie v Pythone - PyHamcrest a best practices

V minulej lekcii, Prvý unit test v unittest frameworku , sme si uviedli základy frameworku unittest a napísali náš prvý unit test.

V dnešnej lekcii unit testy dokončíme, uvedieme si knižnicu PyHamcrest, ktorá zásadne mení spôsob, akým píšeme asserce. Ďalej si uvedieme metódu očakávanej chyby a na záver spomenieme best practices na písanie jednotkových testov.

PyHamcrest matchers

Knižnica PyHamcrest nie je súčasťou základného balíčka, musíme si ju preto najskôr nainštalovať. Vo virtuálnom prostredí zadáme do terminálu príkaz:
pip install pyhamcrest

V prípade úspešnej inštalácie bude výstup vyzerať takto:

Windows PowerShell
Collecting pyhamcrest
Downloading PyHamcrest-2.0.3-py3-none-any.whl (51 kB)
     |████████████████████████████████| 51 kB 208 kB/s
Installing collected packages: pyhamcrest
Successfully installed pyhamcrest-2.0.3

V knižnici PyHamcrest sa okrem iného nachádza metóda assertThat(), ktorá prináša nový spôsob pre asserciu, tzv. matchers. Kombinovaním týchto matcherov môžeme ľahko docieliť vytvorenie aj zložitejšieho výrazu. Asi vás neprekvapí, že matchery sú opäť triedy, na ktorých sa nachádzajú statické metódy, tie môžeme reťaziť a tiež je možné napísať vlastné matchery. Výsledkom používania tejto novej metódy je vyššia čitateľnosť. Zoznam preddefinovaných matcherov je k dispozícii na PyHamcrest Matcher library, celá dokumentácia potom na stránke PyHamcrest dokumentácie.

Ukážme si, ako by vyzerala jednoduchá asserce v pôvodnej syntaxi:

assertEquals(2, kalkulacka.secti(1, 1))

A teraz v PyHamcrest:

assert_that(Kalkulacka.secti(1, 1), equal_to(2))

Keď si odmyslíme čecho-angličtinu, ktorú tu používam rovnako len na akademické účely (v praxi budeme mať všetko po anglicky), dostali sme súvislé vety. Tieto vety získame aj vo výsledkoch testov.

Pokiaľ vezmeme súbor test_kalkulacka.py z predchádzajúcej lekcie a testy prepíšeme do syntaxe PyHamcrest, budú vyzerať takto (nesmieme zabudnúť dodať potrebné importy pre knižnicu PyHamcrest - pre istotu kód prikladám vrátane spomínaných importov):

import unittest
from kalkulacka import Kalkulacka
from hamcrest import assert_that, equal_to, close_to, calling, raises

class TestKalkulacka(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        pass

    @classmethod
    def tearDownClass(cls):
        pass

    def setUp(self):
        from kalkulacka import Kalkulacka
        Kalkulacka = Kalkulacka()

    def tearDown(self):
        pass

    def test_secti(self):
        assert_that(Kalkulacka.secti(1, 1), equal_to(2))
        assert_that(Kalkulacka.secti(3.14, -1.72), close_to(1.42, 3))
        assert_that(Kalkulacka.secti(1.0/3, 1.0/3), close_to(2.0/3, 3))

    def test_odecti(self):
        assert_that(Kalkulacka.odecti(1, 1), equal_to(0))
        assert_that(Kalkulacka.odecti(3.14, -1.72), close_to(4.86, 3))
        assert_that(Kalkulacka.odecti(1.0/3, -1.0/3), close_to(2.0/3, 3))

    def test_vynasob(self):
        assert_that(Kalkulacka.vynasob(1, 2), equal_to(2))
        assert_that(Kalkulacka.vynasob(3.14, -1.72), close_to(-5.4008, 3))
        assert_that(Kalkulacka.vynasob(1.0/3, 1.0/3), close_to(0.111, 3))

    def test_vydel(self):
        assert_that(Kalkulacka.vydel(4, 2), equal_to(2))
        assert_that(Kalkulacka.vydel(3.14, -1.72), close_to(-1.826, 3))
        assert_that(Kalkulacka.vydel(1.0/3, 1.0/3), equal_to(1))

    def test_vydel_nulou(self):
        assert_that(calling(Kalkulacka.vydel).with_args(2, 0), raises(ValueError))

Môžete si vyskúšať, že funguje rovnako dobre ako ten predtým. Testy spustíme obvyklým spôsobom, do terminálu zadáme:

python -m unittest -v test_kalkulacka.py

Opäť všetky testy prešli, výsledok vyzerá takto:

Windows PowerShell
test_odecti (test_kalkulacka.TestKalkulacka) ... ok
test_secti (test_kalkulacka.TestKalkulacka) ... ok
test_vydel (test_kalkulacka.TestKalkulacka) ... ok
test_vydel_nulou (test_kalkulacka.TestKalkulacka) ... ok
test_vynasob (test_kalkulacka.TestKalkulacka) ... ok
----------------------------------------------------------------------
Ran 5 tests in 0.002s
OK

Či budeme používať predvolený alebo nový spôsob asercií je už na každom z nás.

Expected failure

Vráťme sa k pôvodnému unittestu. Stále je tu funkcionalita, ktorú sme si ešte neuviedli. Od verzie 3.1 unittest podporuje označovanie testov ako "expected failure" alebo očakávané chyby. V tomto prípade očakávame, že test neprejde, ale nebude sa počítať za chybu. Naopak, ak by prešiel, obdržali by sme chybovú hlášku "unexpected success". Pred metódou musíme použiť dekorátor @expectedFailure. Ukážme si to na príklade:
class OcekavanaChybaTestu(unittest.TestCase):
    @unittest.expectedFailure
    def test_porovnej_cislo_a_retezec(self):
        self.assertEqual(1, '1')

Test prejde a dostaneme výsledok:

Windows PowerShell
test_porovnej_cislo_a_retezec (test_kalkulacka.OcekavanaChybaTestu) ... expected failure

Pokiaľ vo funkcii zmeníme výsledok za správny:

self.assertEqual(1, 1)

Dostaneme túto chybovú hlášku:

Windows PowerShell
test_porovnej_cislo_a_retezec (test_kalkulacka.OcekavanaChybaTestu) ... unexpected success
FAILED (unexpected successes=1)

Daná funkcia nám umožňuje modifikovať ako sa metódy spúšťajú a vyhodnocujú. Môže teda napr. prejsť aj metóda, ktorá by za normálnych okolností neprešla. To isté by sme mohli teoreticky robiť aj v setUp() / tearDown() metódach, ktoré sa spúšťajú pred/po každom teste, ale pomocou predpripravených pravidiel to je oveľa jednoduchšie.

Best practices

Už v minulej lekcii sme načali best practices. Keďže to je k unit testom v Pythone všetko, poďme si na záver vymenovať akých častých chýb sa vyvarovať, aby sme dosiahli kvalitný výsledok:
  • Testujeme špecifikáciu, nie kód. Testy nikdy nepíšeme podľa kódu nejakej metódy, ale zamýšľame sa nad tým, na čo metóda reálne slúži a čo všetko ju môže prísť ako vstup.
  • Testujeme všeobecné knižnice, nie konkrétnu logiku aplikácie. Ak je logika dôležitá a všeobecná, mala by byť vyčlenená do samostatnej knižnice a tá by mala byť potom testovaná.
  • Každý test by mal byť úplne nezávislý od ostatných testov. Scenár by mal prebehnúť aj keď metódy ľubovoľne prehádžeme a žiadna metóda by po sebe nemala zanechávať nejaké zmeny (v súboroch, v databáze a podobne), ktoré by ovplyvnili ďalšie metódy. Na dosiahnutie tohto správania často pripravujeme prostredie pre jednotlivé metódy v setUp() metódach a prípadne po nich ešte vykonáme upratovanie v tearDown() metódach. To isté platí aj pre celé testy.
  • Každý test by mal dopadnúť vždy rovnako, bez ohľadu na to, kedy ho spustíme. Pozor na testovanie generátorov náhodných výstupov a na prácu s dátumom a časom.
  • Nevykonávajme duplicitné porovnanie. Pokiaľ nejaký vstup už overuje iný test, nevykonávajme toto overenie znova.
  • Každý scenár testuje len jednu jednotku (triedu). Náš softvér by mal byť navrhnutý tak, aby bol rozdelený na menšie triedy, ktoré majú minimálne závislosti na ostatných a preto sa dajú jednoducho a nezávisle testovať (vzory high cohesion a low coupling).
  • Ak testy vyžadujú externé služby, mali by sme ich tzv. mockovať. Tým vytvárame „falošné“ služby s rovnakým rozhraním, ktoré obvykle len podstrkujú testovacie dáta. Využitím skutočných služieb by sme porušili nezávislosť testov, pretože by sa navzájom začali ovplyvňovať. Menej elegantné riešenie je vždy na začiatku nastaviť a na konci vrátiť stav služieb.
  • Ako platí aj všade inde, vyhnime sa zavádzajúcim názvom testov (ako test_vypocet(), test_vyjimka() a podobne). Programátori často pomenovávajú testy aj väčším počtom slov, aby sa poznalo čo robia. Bežne by sme to pri metódach nemali robiť, pretože každá metóda robí len jednu činnosť, ale pri testoch dáva niekedy zmysel pomenovať metódy obskurne, napr. test_vypocti_kvadratickou_rovnici_a_zadej_zaporne_koeficienty(), pretože test často testuje viac vstupov. Ideálne by mal názov testu obsahovať názov metódy, ktorú testuje. V pomenovávaní testov by sme mali byť konzistentné. Nebojme sa ani komentárov.
  • Testy by mali prebehnúť rýchlo, pretože v praxi obvykle testujeme všetky časti aplikácie rôznymi typmi testov a všetky časy sa dokážu nasčítať do nepríjemnej pauzy.

Naše prvé unit testy nemusia byť perfektné, stačí krátko otestovať to najdôležitejšie. Uvidíme, že nám časom začnú zlyhávať a odhaľovať chyby v implementácii. Čím je aplikácia väčšia, tým o väčšie pokrytie testami (test code coverage) by sme sa mali snažiť.

V budúcej lekcii, Testovanie v Pythone - Unit testy - Test analyzeru tagov , sa pozrieme na akceptačné testy.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 7x (3.37 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python

 

Predchádzajúci článok
Prvý unit test v unittest frameworku
Všetky články v sekcii
Testovanie v Pythone
Preskočiť článok
(neodporúčame)
Testovanie v Pythone - Unit testy - Test analyzeru tagov
Článok pre vás napísal Patrik Bernat
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Aktivity