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 vtearDown()
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