Obsah
1. Nástroj pytest a jednotkové testy: fixtures, výjimky, parametrizace testů
2. Chování jednotkových testů ve chvíli, kdy testovaná jednotka obsahuje chybu
3. Použití vlastní chybové zprávy ve chvíli, kdy není podmínka splněna
4. Otestování chování funkce pro výpočet průměru pro prázdný vstup
5. Detekce, jestli došlo v testované jednotce k vyhození výjimky určitého typu
6. Objekt nesoucí informace o vyhozené výjimce
7. Parametrizace jednotkových testů
8. Dekorátor @pytest.mark.parametrize
9. Specifikace identifikátorů použitých při konstrukci jména testu
10. Umístění hodnot parametrů přímo do dekorátoru @pytest.mark.parametrize
11. Výpis výjimky, která vznikne v testovaném bloku či v blocích volaných
12. Výběr jednotkových testů pro spuštění na základě jejich jména
13. Výběr jednotkových testů na základě zadané značky
14. Registrace nových značek v souboru pytest.ini
15. Registrace nových značek přímo v jednotkovém testu
17. Obsah následující části seriálu
18. Repositář s demonstračními příklady
19. Předchozí články s tématem testování (nejenom) v Pythonu
1. Použití nástroje pytest pro tvorbu jednotkových testů: fixtures, výjimky, životní cyklus testu
V první části článku budeme s využitím jednotkových testů ověřovat funkčnost velmi jednoduché funkce, která pro zadaný vstup (seznam, n-tici) obsahující numerické hodnoty vypočte průměr. Využívá se zde dvojice funkcí, které jsou součástí základní knihovny programovacího jazyka Python, jenž je dostupná ihned po inicializaci a spuštění interpretru. Námi vytvořená funkce se jmenuje average a prozatím nijak netestuje svůj vstup:
"""Výpočet průměru.""" def average(x): """Výpočet průměru ze seznamu hodnot předaných v parametru x.""" return sum(x)/float(len(x))
Základní funkcionalitu modulu average, v němž je stejnojmenná funkce deklarována, můžeme velmi rychle ověřit spuštěním následujícího skriptu a pohledem na standardní výstup:
#!/usr/bin/env python3 """Vstupní bod do testované aplikace.""" from average import * if __name__ == '__main__': # pouze se ujistíme, že lze spustit funkci average print(average([1, 2]))
Samozřejmě je ovšem lepší vytvořit si sadu jednotkových testů, které nám umožní ověřovat funkcionalitu a (bez)chybnost automaticky, například při každé ucelenější změně programového kódu (což většinou odpovídá jednomu commitu do repositáře). První verze jednotkových testů (uložená v souboru začínajícím na test_) je prozatím triviální, protože zavolá testovanou funkci jen jedenkrát a zjistí výsledek tohoto volání:
"""Implementace jednotkových testů.""" from average import average def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) assert result == 1.5
Jednotkové testy můžeme spustit snadno:
$ pytest
S následujícím výsledkem:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average01 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collected 1 item test_average.py . [100%] ============================== 1 passed in 0.02s ===============================
Osobně ovšem dávám přednost poněkud podrobnějším výsledkům s výpisem všech funkcí a metod, v nichž jsou jednotlivé testy implementovány. Toto chování zajistí přepínač -v:
$ pytest -v
Nyní se zobrazí i jméno testovací funkce, tedy test_average_basic:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average01 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 1 item test_average.py::test_average_basic PASSED [100%] ============================== 1 passed in 0.02s ===============================
Popř. si můžeme zobrazit, do jaké míry jsou zdrojové kódy pokryty jednotkovými testy:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average01 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collected 1 item test_average.py . [100%] ----------- coverage: platform linux, python 3.6.6-final-0 ----------- Name Stmts Miss Cover Missing ------------------------------------------ average.py 2 0 100% ============================== 1 passed in 0.04s ===============================
První demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average01/.
2. Chování jednotkových testů ve chvíli, kdy testovaná jednotka obsahuje chybu
Podívejme se nyní na to, jak se budou jednotkové testy chovat ve chvíli, kdy se testovaná jednotka (což je v našem případě funkce pro výpočet průměru) v důsledku chyby zanesené programátorem začne chovat nekorektně. Funkce je naschvál upravena tak, že při výpočtu průměru zvyšuje dělitel o jedničku (viz podtržený kód):
"""Výpočet průměru.""" def average(x): """Výpočet průměru ze seznamu hodnot předaných v parametru x.""" return sum(x)/float(1+len(x))
Jednotkové testy ovšem zůstanou nezměněny, což zde konkrétně znamená, že pro vstupní hodnoty 1 a 2 (předané prozatím v seznamu nebo n-tici) očekává výsledek 3/2:
"""Implementace jednotkových testů.""" from average import average def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) assert result == 1.5
Nyní dojde – zcela podle očekávání – k detekci chyby při spuštění jednotkových testů:
$ pytest -v
Zajímavé je zjistit, jak vlastně vypadá výstup z nástroje pytest ve chvíli, kdy k chybě dojde:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average02 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 1 item test_average.py::test_average_basic FAILED [100%] =================================== FAILURES =================================== ______________________________ test_average_basic ______________________________ def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) > assert result == 1.5 E assert 1.0 == 1.5 E +1.0 E -1.5 test_average.py:9: AssertionError =========================== short test summary info ============================ FAILED test_average.py::test_average_basic - assert 1.0 == 1.5 ============================== 1 failed in 0.04s ===============================
Povšimněte si, že se vypíše jak vlastní jednotkový test, tak i chyba a to relativně „chytrým“ způsobem: zobrazením aserce (popř. kódu, kde vznikla výjimka) a poté porovnáním očekávaného výsledku a výsledku aktuálního:
> assert result == 1.5 E assert 1.0 == 1.5 E +1.0 E -1.5
Tyto dvě hodnoty jsou zobrazeny ve formě podobné unifikovanému diffu, což nás může v tomto konkrétním případě mást, protože –1.5 neznamená zápornou hodnotu, ale hodnotu –1.5, před kterou je umístěn znak „-“ znamenající „chybí/je nahrazeno“:
3F F8 00 00 00 00 00 00
Druhý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average02/.
3. Použití vlastní chybové zprávy ve chvíli, kdy není podmínka splněna
V našem demonstračním příkladu je podmínka (resp. přesněji řečeno aserce) umístěná v jednotkovém testu zcela zřejmá a programátorovi by nemělo potíže zjistit, co a proč se testuje. Ovšem společně se stoupajícím množstvím jednotkových testů (a v reálné aplikaci jich mohou být stovky i tisíce) je vhodné čtenářům výsledků testů situaci co nejvíce usnadnit a přidat do testů lidsky čitelnou zprávu. To konstrukce assert umožňuje, protože za podmínkou může být umístěn buď řetězec nebo výraz, který se na řetězec může převést (typicky aplikací šablony). Tento řetězec je použit pro výpis chybové zprávy ve chvíli, kdy podmínka není splněna a tudíž jednotkový test našel potenciální chybu. Jednoduchý příklad:
"""Implementace jednotkových testů.""" from average import average def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) expected = 1.5 assert result == expected, "Očekávaná hodnota při výpočtu průměru hodnot 1 a 2 má být {}, vráceno {}".format(expected, result)
Pokud nyní tento jednotkový test spustíme, bude chybové hlášení (vypsáno tučně) obsahovat i kontext, který může uživateli pomoci při zjišťování, k jaké chybě vlastně došlo. Stejný text se zobrazí i v přehledu na konci:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average03 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 1 item test_average.py::test_average_basic FAILED [100%] =================================== FAILURES =================================== ______________________________ test_average_basic ______________________________ def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) expected = 1.5 > assert result == expected, "Očekávaná hodnota při výpočtu průměru hodnot 1 a 2 má být {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota při výpočtu průměru hodnot 1 a 2 má být 1.5, vráceno 1.0 E assert 1.0 == 1.5 E +1.0 E -1.5 test_average.py:10: AssertionError ================================== short test summary info ======================================== FAILED test_average.py::test_average_basic - AssertionError: Očekávaná hodnota ... 1.5, vráceno 1.0 ===================================== 1 failed in 0.04s ===========================================
Třetí demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average03/.
4. Otestování chování funkce pro výpočet průměru pro prázdný vstup
Sadu jednotkových testů si můžeme velmi snadno rozšířit o další testy zjišťující chování testované funkce v mezních případech. Například by bylo vhodné zjistit, jak se funkce pro výpočet průměru chová ve chvíli, kdy jí nejsou předány žádné hodnoty, resp. přesněji řečeno když se jí předá prázdný seznam nebo prázdná n-tice:
"""Implementace jednotkových testů.""" from average import average def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) expected = 1.5 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_average_empty_list(): """Otestování výpočtu průměru.""" result = average([]) expected = 0.0 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
Nyní jednotkové testy znovu spustíme:
$ pytest -v
A podíváme se na jejich výsledky:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average04 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 2 items test_average.py::test_average_basic PASSED [ 50%] test_average.py::test_average_empty_list FAILED [100%] =================================== FAILURES =================================== ___________________________ test_average_empty_list ____________________________ def test_average_empty_list(): """Otestování výpočtu průměru.""" > result = average([]) test_average.py:15: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ x = [] def average(x): > return sum(x)/float(len(x)) E ZeroDivisionError: float division by zero average.py:2: ZeroDivisionError =========================== short test summary info ============================ FAILED test_average.py::test_average_empty_list - ZeroDivisionError: float di... ========================= 1 failed, 1 passed in 0.04s ==========================
V tomto případě došlo k vyhození výjimky typu ZeroDivisionError, protože se při výpočtu průměru dělí nulou. V navazující kapitole si ukážeme, jakým způsobem je možné tuto výjimku odchytit (pokud budeme považovat toto chování testované funkce za žádoucí).
Čtvrtý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average04/.
5. Detekce, jestli došlo v testované jednotce k vyhození výjimky určitého typu
Knihovna pytest umožňuje velmi elegantním způsobem napsat jednotkový test s detekcí, zda v testované jednotce (v našem případě ve funkci) došlo či naopak nedošlo k vyhození výjimky, a to dokonce výjimky s určitým typem. Pro tento účel se používá řídicí struktura with společně s pytest.raises():
with pytest.raises(ZeroDivisionError) as excinfo: result = average([])
V této konstrukci je ZeroDivisionError typ očekávané výjimky a excinfo objekt, který bude obsahovat podrobnější informace o výjimce. Pokud k výjimce skutečně dojde, bude test úspěšný, pokud ovšem nedojde, nahlásí se chyba. Obě tyto možnosti jsou ukázány v dalším zdrojovém kódu jednotkových testů:
"""Implementace jednotkových testů.""" import pytest from average import average def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) expected = 1.5 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_average_empty_list(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(ZeroDivisionError) as excinfo: result = average([]) def test_average_exception_not_raised(): """Otestování výpočtu průměru.""" with pytest.raises(ZeroDivisionError) as excinfo: result = average([1, 2])
První jednotkový test již známe, druhý a třetí pak očekává výjimku. Ovšem jen ve druhém testu skutečně dojde ke vzniku výjimky, což je ostatně patrné i z výsledků běhu:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average05 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 3 items test_average.py::test_average_basic PASSED [ 33%] test_average.py::test_average_empty_list PASSED [ 66%] test_average.py::test_average_exception_not_raised FAILED [100%] =================================== FAILURES =================================== ______________________ test_average_exception_not_raised _______________________ def test_average_exception_not_raised(): """Otestování výpočtu průměru.""" with pytest.raises(ZeroDivisionError) as excinfo: > result = average([1, 2]) E Failed: DID NOT RAISE <class 'ZeroDivisionError'> test_average.py:24: Failed =========================== short test summary info ============================ FAILED test_average.py::test_average_exception_not_raised - Failed: DID NOT R... ========================= 1 failed, 2 passed in 0.04s ==========================
Pátý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average05/.
6. Objekt nesoucí informace o vyhozené výjimce
V programové konstrukci with, v níž se očekává vyhození výjimky, se mj. při splnění podmínky (výjimka vznikla) naplní i proměnná, kterou jsme pojmenovali excinfo:
with pytest.raises(Exception) as excinfo: result = average([])
Tato proměnná obsahuje několik důležitých údajů o výjimce uložených ve formě běžných atributů objektu, zejména pak:
- type
- value
- traceback
Můžeme tedy psát (což je ovšem velmi umělá konstrukce) například:
with pytest.raises(Exception) as excinfo: result = average([]) # poměrně křehký způsob testování! assert excinfo.type == ZeroDivisionError assert str(excinfo.value) == "float division by zero"
Na druhou stranu je někdy nutné testovat i hodnotu předávanou v objektu představujícím výjimku:
with pytest.raises(ZeroDivisionError) as excinfo: result = average([]) assert str(excinfo.value) == "float division by zero"
V šestém příkladu budou všechny testy provedeny, a to aniž by byla detekována chyba v testované funkci:
"""Implementace jednotkových testů.""" import pytest from average import average def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) expected = 1.5 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_average_empty_list_1(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(ZeroDivisionError) as excinfo: result = average([]) def test_average_empty_list_2(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(Exception) as excinfo: result = average([]) # poměrně křehký způsob testování! assert excinfo.type == ZeroDivisionError assert str(excinfo.value) == "float division by zero"
Výsledek běhu jednotkových testů:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average06 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 3 items test_average.py::test_average_basic PASSED [ 33%] test_average.py::test_average_empty_list_1 PASSED [ 66%] test_average.py::test_average_empty_list_2 PASSED [100%] ============================== 3 passed in 0.02s ===============================
Šestý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average06/.
7. Parametrizace jednotkových testů
Představme si, že budeme chtít rozšířit následující jednotkový test o další vstupní hodnoty a o očekávané výsledky:
def test_average_basic(): """Otestování výpočtu průměru.""" result = average([1, 2]) expected = 1.5 assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
Pochopitelně je možné vytvořit například tento kód:
def test_average_basic_more_checks(): """Otestování výpočtu průměru.""" inputs = ( (0, 0), (1, 0), (1, 1), (1, 2)) expected_results = (0, 0.5, 1, 1.5) for input, expected in zip(inputs, expected_results): result = average(input) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
Nejedná se ovšem o příliš elegantní ani idiomatický způsob, jak to udělat, protože se přímo v jednotkovém testu kombinuje jak vlastní test, tak i pomocná konstrukce se smyčkou atd. V následujících kapitolách si ukážeme jiné způsoby.
type AddTest struct { x int32 y int32 expected int32 } func checkAdd(t *testing.T, testInputs []AddTest) { for _, i := range testInputs { result := add(i.x, i.y) if result != i.expected { msg := fmt.Sprintf("%d + %d should be %d, got %d instead", i.x, i.y, i.expected, result) t.Error(msg) } } } func TestAddBasicValues(t *testing.T) { var addTestInput = []AddTest{ {0, 0, 0}, {1, 0, 1}, {2, 0, 2}, {2, 1, 3}, } checkAdd(t, addTestInput) }
8. Dekorátor @pytest.mark.parametrize
Ukažme si nyní poněkud lepší způsob, jak zapsat jednotkový test bez nutnosti explicitního procházení seznamem/n-ticí vstupů a očekávaných výsledků. Naši funkci s testem upravíme takovým způsobem, aby akceptovala dva parametry – vstup do testované funkce a její očekávaný výsledek:
def test_average_basic(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
Data pro tento test mohou být reprezentována například n-ticí obsahující další n-tice; zde konkrétně dvojice vstup+očekávaný výsledek:
testdata = [ ((1, 1), 1), ((1, 2), 1.5), ((0, 1), 0.5), ((1, 2, 3), 2.0), ((0, 10), 0.5), ]
Zbývá nám jediné – uvést dekorátor @pytest.mark.parametrize před hlavičku testu:
@pytest.mark.parametrize("values,expected", testdata) def test_average_basic(values, expected): ... ... ...
Vlastní postupné doplnění parametrů zajistí nástroj pytest. Upravený kód jednotkových testů tedy může vypadat následovně:
"""Implementace jednotkových testů.""" import pytest from average import average testdata = [ ((1,1), 1), ((1,2), 1.5), ((0,1), 0.5), ((1,2,3), 2.0), ((0,10), 0.5), ] @pytest.mark.parametrize("values,expected", testdata) def test_average_basic(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_average_empty_list_1(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(ZeroDivisionError) as excinfo: result = average([]) def test_average_empty_list_2(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(Exception) as excinfo: result = average([]) # poměrně křehký způsob testování! assert excinfo.type == ZeroDivisionError assert str(excinfo.value) == "float division by zero"
Z výsledků je patrné, že se vytvořily nové testy rozlišené hodnotou v hranatých závorkách (viz zvýrazněný text):
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average07 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 7 items test_average.py::test_average_basic[values0-1] PASSED [ 14%] test_average.py::test_average_basic[values1-1.5] PASSED [ 28%] test_average.py::test_average_basic[values2-0.5] PASSED [ 42%] test_average.py::test_average_basic[values3-2.0] PASSED [ 57%] test_average.py::test_average_basic[values4-0.5] FAILED [ 71%] test_average.py::test_average_empty_list_1 PASSED [ 85%] test_average.py::test_average_empty_list_2 PASSED [100%] =================================== FAILURES =================================== _______________________ test_average_basic[values4-0.5] ________________________ values = (0, 10), expected = 0.5 @pytest.mark.parametrize("values,expected", testdata) def test_average_basic(values, expected): """Otestování výpočtu průměru.""" result = average(values) > assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota 0.5, vráceno 5.0 E assert 5.0 == 0.5 E +5.0 E -0.5 test_average.py:21: AssertionError =========================== short test summary info ============================ FAILED test_average.py::test_average_basic[values4-0.5] - AssertionError: Oče... ========================= 1 failed, 6 passed in 0.05s ==========================
Sedmý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average07/.
9. Specifikace identifikátorů použitých při konstrukci jména testu
V předchozím výpisu výsledku testů jsme mohli vidět, že se testovací framework pytest pokusil o vytvoření jména testů, do nichž automaticky dosazoval parametry:
test_average.py::test_average_basic[values0-1] PASSED [ 14%] test_average.py::test_average_basic[values1-1.5] PASSED [ 28%] test_average.py::test_average_basic[values2-0.5] PASSED [ 42%] test_average.py::test_average_basic[values3-2.0] PASSED [ 57%] test_average.py::test_average_basic[values4-0.5] FAILED [ 71%]
Tato jména (identifikátory) je ovšem možné specifikovat i přímo v kódu testů, což se v některých případech hodí (například při testování funkce pro login můžeme označit správné a nesprávné heslo atd.). Pro tento účel lze specifikovat atribut ids v dekorátoru @pytest.mark.parametrize:
@pytest.mark.parametrize("values,expected", testdata, ids=["1,1", "1,2", "0,1", "1,2,3", "0,10"]) def test_average_basic_2(values, expected): ... ... ...
Ve výsledku se objeví odlišná jména, která více odpovídají tomu, co vlastně testujeme:
test_average.py::test_average_basic_2[1,1] PASSED [ 14%] test_average.py::test_average_basic_2[1,2] PASSED [ 28%] test_average.py::test_average_basic_2[0,1] PASSED [ 42%] test_average.py::test_average_basic_2[1,2,3] PASSED [ 57%] test_average.py::test_average_basic_2[0,10] FAILED [ 71%]
Pro úplnost se podívejme na úplný zdrojový kód testů:
"""Implementace jednotkových testů.""" import pytest from average import average testdata = [ ((1,1), 1), ((1,2), 1.5), ((0,1), 0.5), ((1,2,3), 2.0), ((0,10), 0.5), ] @pytest.mark.parametrize("values,expected", testdata, ids=["1,1", "1,2", "0,1", "1,2,3", "0,10"]) def test_average_basic_2(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_average_empty_list_1(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(ZeroDivisionError) as excinfo: result = average([]) def test_average_empty_list_2(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(Exception) as excinfo: result = average([]) # poměrně křehký způsob testování! assert excinfo.type == ZeroDivisionError assert str(excinfo.value) == "float division by zero"
Spuštění jednotkových testů opět provedeme s přepínačem -v:
$ pytest -v
S výsledky:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average08 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 7 items test_average.py::test_average_basic_2[1,1] PASSED [ 14%] test_average.py::test_average_basic_2[1,2] PASSED [ 28%] test_average.py::test_average_basic_2[0,1] PASSED [ 42%] test_average.py::test_average_basic_2[1,2,3] PASSED [ 57%] test_average.py::test_average_basic_2[0,10] FAILED [ 71%] test_average.py::test_average_empty_list_1 PASSED [ 85%] test_average.py::test_average_empty_list_2 PASSED [100%] =================================== FAILURES =================================== __________________________ test_average_basic_2[0,10] __________________________ values = (0, 10), expected = 0.5 @pytest.mark.parametrize("values,expected", testdata, ids=["1,1", "1,2", "0,1", "1,2,3", "0,10"]) def test_average_basic_2(values, expected): """Otestování výpočtu průměru.""" result = average(values) > assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota 0.5, vráceno 5.0 E assert 5.0 == 0.5 E +5.0 E -0.5 test_average.py:21: AssertionError =========================== short test summary info ============================ FAILED test_average.py::test_average_basic_2[0,10] - AssertionError: Očekávan... ========================= 1 failed, 6 passed in 0.05s ==========================
Osmý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average08/.
10. Umístění hodnot parametrů přímo do dekorátoru @pytest.mark.parametrize
Alternativně je možné hodnoty parametrů, které se mají předávat do jednotkového testu, zadat přímo do dekorátoru @pytest.mark.parametrize. Povšimněte si, že se v tomto případě nejdříve uvede mapování mezi hodnotami a parametry testu a následuje seznam nebo n-tice objektů typu pytest.param. Celý zápis je tedy nepatrně delší, ovšem umožňuje, aby se celý programový kód související s jedním jednotkovým testem spojil i s testovacími daty:
"""Implementace jednotkových testů.""" import pytest from average import average @pytest.mark.parametrize( "values,expected", [ pytest.param( (1, 1), 1 ), pytest.param( (1, 2), 1.5 ), pytest.param( (0, 1), 0.5 ), pytest.param( (1, 2, 3), 2.0 ), pytest.param( (0, 10), 0.5 ), ], ) def test_average_basic_3(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) def test_average_empty_list_1(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(ZeroDivisionError) as excinfo: result = average([]) def test_average_empty_list_2(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(Exception) as excinfo: result = average([]) # poměrně křehký způsob testování! assert excinfo.type == ZeroDivisionError assert str(excinfo.value) == "float division by zero"
Při pohledu na výsledky jednotkových testů je patrné, že se změnila jména automaticky vytvářených testů:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average09 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 7 items test_average.py::test_average_basic_3[values0-1] PASSED [ 14%] test_average.py::test_average_basic_3[values1-1.5] PASSED [ 28%] test_average.py::test_average_basic_3[values2-0.5] PASSED [ 42%] test_average.py::test_average_basic_3[values3-2.0] PASSED [ 57%] test_average.py::test_average_basic_3[values4-0.5] FAILED [ 71%] test_average.py::test_average_empty_list_1 PASSED [ 85%] test_average.py::test_average_empty_list_2 PASSED [100%] =================================== FAILURES =================================== ______________________ test_average_basic_3[values4-0.5] _______________________ values = (0, 10), expected = 0.5 @pytest.mark.parametrize( "values,expected", [ pytest.param( (1,1), 1 ), pytest.param( (1,2), 1.5 ), pytest.param( (0,1), 0.5 ), pytest.param( (1,2,3), 2.0 ), pytest.param( (0,10), 0.5 ), ], ) def test_average_basic_3(values, expected): """Otestování výpočtu průměru.""" result = average(values) > assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota 0.5, vráceno 5.0 E assert 5.0 == 0.5 E +5.0 E -0.5 test_average.py:31: AssertionError =========================== short test summary info ============================ FAILED test_average.py::test_average_basic_3[values4-0.5] - AssertionError: O... ========================= 1 failed, 6 passed in 0.05s ==========================
Devátý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average09/.
Alternativní zápis s n-ticí namísto seznamu (což je z hlediska sémantiky lepší řešení):
@pytest.mark.parametrize( "values,expected", ( pytest.param( (1, 1), 1 ), pytest.param( (1, 2), 1.5 ), pytest.param( (0, 1), 0.5 ), pytest.param( (1, 2, 3), 2.0 ), pytest.param( (0, 10), 0.5 ), ), ) def test_average_basic_3(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
11. Výpis výjimky, která vznikne v testovaném bloku či v blocích volaných
Nyní funkci pro výpočet průměru upravíme – a to velmi umělým způsobem. Zařídíme totiž, že se vlastní výpočet bude provádět ve funkci nazvané f4, ovšem sekvence volání pro získání kýženého výsledku bude složitější: average→f1→f2→f3→f4:výpočet. Vyzkoušíme si tak, jakým způsobem bude nástroj pytest reagovat na situaci, kdy výjimka vznikne až ve funkci f4, která se nikde z jednotkových testů přímo nevolá:
"""Výpočet průměru špagetovým kódem.""" def average(x): """Výpočet průměru ze seznamu hodnot předaných v parametru x.""" return f1(x) def f1(x): return f2(x) def f2(x): return f3(x) def f3(x): return f4(x) def f4(x): return sum(x)/float(len(x))
V jednotkových testech kromě jiného zjišťujeme, co se stane ve chvíli, kdy se má vypočítat průměr z prázdného seznamu nebo prázdné n-tice. Tehdy je vyhozena výjimka, která „probublá“ z funkce nazvané f4 přes funkci average až do vlastního jednotkového testu:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average10 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 18 items test_average.py::test_average_basic_1[values0-1] PASSED [ 5%] test_average.py::test_average_basic_1[values1-1.5] PASSED [ 11%] test_average.py::test_average_basic_1[values2-0.5] PASSED [ 16%] test_average.py::test_average_basic_1[values3-2.0] PASSED [ 22%] test_average.py::test_average_basic_1[values4-0.5] FAILED [ 27%] test_average.py::test_average_basic_2[1,1] PASSED [ 33%] test_average.py::test_average_basic_2[1,2] PASSED [ 38%] test_average.py::test_average_basic_2[0,1] PASSED [ 44%] test_average.py::test_average_basic_2[1,2,3] PASSED [ 50%] test_average.py::test_average_basic_2[0,10] FAILED [ 55%] test_average.py::test_average_basic_3[values0-1] PASSED [ 61%] test_average.py::test_average_basic_3[values1-1.5] PASSED [ 66%] test_average.py::test_average_basic_3[values2-0.5] PASSED [ 72%] test_average.py::test_average_basic_3[values3-2.0] PASSED [ 77%] test_average.py::test_average_basic_3[values4-0.5] FAILED [ 83%] test_average.py::test_average_basic_3[values5-0] FAILED [ 88%] test_average.py::test_average_empty_list_1 PASSED [ 94%] test_average.py::test_average_empty_list_2 PASSED [100%] =================================== FAILURES =================================== ______________________ test_average_basic_1[values4-0.5] _______________________ ... ... ... __________________________ test_average_basic_2[0,10] __________________________ ... ... ... ______________________ test_average_basic_3[values4-0.5] _______________________ ... ... ... _______________________ test_average_basic_3[values5-0] ________________________ ... ... ... def test_average_basic_3(values, expected): """Otestování výpočtu průměru.""" > result = average(values) test_average.py:56: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ average.py:5: in average return f1(x) average.py:8: in f1 return f2(x) average.py:11: in f2 return f3(x) average.py:14: in f3 return f4(x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ x = () def f4(x): > return sum(x)/float(len(x)) E ZeroDivisionError: float division by zero average.py:17: ZeroDivisionError =========================== short test summary info ============================ FAILED test_average.py::test_average_basic_1[values4-0.5] - AssertionError: O... FAILED test_average.py::test_average_basic_2[0,10] - AssertionError: Očekávan... FAILED test_average.py::test_average_basic_3[values4-0.5] - AssertionError: O... FAILED test_average.py::test_average_basic_3[values5-0] - ZeroDivisionError: ... ========================= 4 failed, 14 passed in 0.09s =========================
Povšimněte si, že informaci o výjimce dostaneme s prakticky celým výpisem zásobníkových rámců, což pochopitelně zjednodušuje zjišťování kde a proč vlastně k výjimce došlo:
test_average.py:56: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ average.py:5: in average return f1(x) average.py:8: in f1 return f2(x) average.py:11: in f2 return f3(x) average.py:14: in f3 return f4(x)
Desátý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average10/.
12. Výběr jednotkových testů pro spuštění na základě jejich jména
Pokud jsou dodrženy nějaké jednotné konvence pojmenování funkcí (nebo metod) s realizací jednotlivých jednotkových testů, je možné na příkazové řádce specifikovat, které testy se mají spustit. Výběr testů může být proveden na základě části jejich jména, což je varianta, kterou si ukážeme v této kapitole. Testy, které zadanému vzoru neodpovídají, jsou přeskočeny.
Spuštění testů, v jejichž jménu se objevuje slovo „basic“:
$ pytest -v -k basic
Výsledky:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average11 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 18 items / 2 deselected / 16 selected test_average.py::test_average_basic_1[values0-1] PASSED [ 6%] test_average.py::test_average_basic_1[values1-1.5] PASSED [ 12%] test_average.py::test_average_basic_1[values2-0.5] PASSED [ 18%] test_average.py::test_average_basic_1[values3-2.0] PASSED [ 25%] test_average.py::test_average_basic_1[values4-0.5] FAILED [ 31%] test_average.py::test_average_basic_2[1,1] PASSED [ 37%] test_average.py::test_average_basic_2[1,2] PASSED [ 43%] test_average.py::test_average_basic_2[0,1] PASSED [ 50%] test_average.py::test_average_basic_2[1,2,3] PASSED [ 56%] test_average.py::test_average_basic_2[0,10] FAILED [ 62%] test_average.py::test_average_basic_3[values0-1] PASSED [ 68%] test_average.py::test_average_basic_3[values1-1.5] PASSED [ 75%] test_average.py::test_average_basic_3[values2-0.5] PASSED [ 81%] test_average.py::test_average_basic_3[values3-2.0] PASSED [ 87%] test_average.py::test_average_basic_3[values4-0.5] FAILED [ 93%] test_average.py::test_average_basic_3[values5-0] FAILED [100%] =================================== FAILURES =================================== ______________________ test_average_basic_1[values4-0.5] _______________________ values = (0, 10), expected = 0.5 @pytest.mark.smoketest @pytest.mark.parametrize("values,expected", testdata) def test_average_basic_1(values, expected): """Otestování výpočtu průměru.""" result = average(values) > assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota 0.5, vráceno 5.0 E assert 5.0 == 0.5 E +5.0 E -0.5 test_average.py:28: AssertionError __________________________ test_average_basic_2[0,10] __________________________ values = (0, 10), expected = 0.5 @pytest.mark.smoketest @pytest.mark.parametrize("values,expected", testdata, ids=["1,1", "1,2", "0,1", "1,2,3", "0,10"]) def test_average_basic_2(values, expected): """Otestování výpočtu průměru.""" result = average(values) > assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota 0.5, vráceno 5.0 E assert 5.0 == 0.5 E +5.0 E -0.5 test_average.py:36: AssertionError ______________________ test_average_basic_3[values4-0.5] _______________________ values = (0, 10), expected = 0.5 @pytest.mark.smoketest @pytest.mark.parametrize( "values,expected", [ pytest.param( (1, 1), 1 ), pytest.param( (1, 2), 1.5 ), pytest.param( (0, 1), 0.5 ), pytest.param( (1, 2, 3), 2.0 ), pytest.param( (0, 10), 0.5 ), pytest.param( (), 0 ), ], ) def test_average_basic_3(values, expected): """Otestování výpočtu průměru.""" result = average(values) > assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) E AssertionError: Očekávaná hodnota 0.5, vráceno 5.0 E assert 5.0 == 0.5 E +5.0 E -0.5 test_average.py:66: AssertionError _______________________ test_average_basic_3[values5-0] ________________________ values = (), expected = 0 @pytest.mark.smoketest @pytest.mark.parametrize( "values,expected", [ pytest.param( (1, 1), 1 ), pytest.param( (1, 2), 1.5 ), pytest.param( (0, 1), 0.5 ), pytest.param( (1, 2, 3), 2.0 ), pytest.param( (0, 10), 0.5 ), pytest.param( (), 0 ), ], ) def test_average_basic_3(values, expected): """Otestování výpočtu průměru.""" > result = average(values) test_average.py:65: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ average.py:6: in average return f1(x) average.py:10: in f1 return f2(x) average.py:14: in f2 return f3(x) average.py:18: in f3 return f4(x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ x = () def f4(x): > return sum(x)/float(len(x)) E ZeroDivisionError: float division by zero average.py:22: ZeroDivisionError =============================== warnings summary =============================== test_average.py:23 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:23: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:31 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:31: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:39 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:39: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:69 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:69: PytestUnknownMarkWarning: Unknown pytest.mark.thorough - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.thorough test_average.py:76 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:76: PytestUnknownMarkWarning: Unknown pytest.mark.thorough - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.thorough -- Docs: https://docs.pytest.org/en/latest/warnings.html =========================== short test summary info ============================ FAILED test_average.py::test_average_basic_1[values4-0.5] - AssertionError: O... FAILED test_average.py::test_average_basic_2[0,10] - AssertionError: Očekávan... FAILED test_average.py::test_average_basic_3[values4-0.5] - AssertionError: O... FAILED test_average.py::test_average_basic_3[values5-0] - ZeroDivisionError: ... ============ 4 failed, 12 passed, 2 deselected, 5 warnings in 0.08s ============
Spuštění testů, v jejichž jménu se objevuje slovo „empty“:
$ pytest -v -k empty
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average11 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 18 items / 16 deselected / 2 selected test_average.py::test_average_empty_list_1 PASSED [ 50%] test_average.py::test_average_empty_list_2 PASSED [100%] =============================== warnings summary =============================== test_average.py:23 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:23: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:31 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:31: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:39 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:39: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:69 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:69: PytestUnknownMarkWarning: Unknown pytest.mark.thorough - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.thorough test_average.py:76 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:76: PytestUnknownMarkWarning: Unknown pytest.mark.thorough - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.thorough -- Docs: https://docs.pytest.org/en/latest/warnings.html ================= 2 passed, 16 deselected, 5 warnings in 0.02s =================
Spuštění testů, v jejichž jménu se objevuje slovo „unknown“:
$ pytest -v -k unknown
Výsledky:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average11 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 18 items / 18 deselected =============================== warnings summary =============================== test_average.py:23 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:23: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:31 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:31: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:39 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:39: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest test_average.py:69 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:69: PytestUnknownMarkWarning: Unknown pytest.mark.thorough - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.thorough test_average.py:76 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:76: PytestUnknownMarkWarning: Unknown pytest.mark.thorough - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.thorough -- Docs: https://docs.pytest.org/en/latest/warnings.html ====================== 18 deselected, 5 warnings in 0.02s ======================
Jedenáctý demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average11/.
13. Výběr jednotkových testů na základě zadané značky
Jednotkové testy je možné vybrat i na základě zadané značky. Ta se opět specifikuje dekorátorem a to například následujícím způsobem:
@pytest.mark.smoketest @pytest.mark.parametrize("values,expected", testdata) def test_average_basic_1(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
V tomto případě je smoketest značkou, s jejímž využitím je možné test či testy vybírat. Takové značky pochopitelně můžeme dopsat i k dalším testům:
"""Implementace jednotkových testů.""" import pytest from average import average def pytest_configure(config): """Konfigurace jednotkových testů.""" config.addinivalue_line( "markers", "smoketest: mark test that are performed very smoketest" ) testdata = [ ((1, 1), 1), ((1, 2), 1.5), ((0, 1), 0.5), ((1, 2, 3), 2.0), ((0, 10), 0.5), ] @pytest.mark.smoketest @pytest.mark.parametrize("values,expected", testdata) def test_average_basic_1(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) @pytest.mark.smoketest @pytest.mark.parametrize("values,expected", testdata, ids=["1,1", "1,2", "0,1", "1,2,3", "0,10"]) def test_average_basic_2(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) @pytest.mark.smoketest @pytest.mark.parametrize( "values,expected", [ pytest.param( (1, 1), 1 ), pytest.param( (1, 2), 1.5 ), pytest.param( (0, 1), 0.5 ), pytest.param( (1, 2, 3), 2.0 ), pytest.param( (0, 10), 0.5 ), pytest.param( (), 0 ), ], ) def test_average_basic_3(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) @pytest.mark.thorough def test_average_empty_list_1(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(ZeroDivisionError) as excinfo: result = average([]) @pytest.mark.thorough def test_average_empty_list_2(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(Exception) as excinfo: result = average([]) # poměrně křehký způsob testování! assert excinfo.type == ZeroDivisionError assert str(excinfo.value) == "float division by zero"
Značka se při výběru testů pro spouštění zadává za přepínačem -m:
$ pytest -v -m smoketest $ pytest -v -m thorough $ pytest -v -m other
Příklad výsledků si uvedeme pouze pro první možnost, tj. pro testy označené značkou smoketest:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/average11 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collecting ... collected 18 items / 2 deselected / 16 selected test_average.py::test_average_basic_1[values0-1] PASSED [ 6%] test_average.py::test_average_basic_1[values1-1.5] PASSED [ 12%] test_average.py::test_average_basic_1[values2-0.5] PASSED [ 18%] test_average.py::test_average_basic_1[values3-2.0] PASSED [ 25%] test_average.py::test_average_basic_1[values4-0.5] FAILED [ 31%] test_average.py::test_average_basic_2[1,1] PASSED [ 37%] test_average.py::test_average_basic_2[1,2] PASSED [ 43%] test_average.py::test_average_basic_2[0,1] PASSED [ 50%] test_average.py::test_average_basic_2[1,2,3] PASSED [ 56%] test_average.py::test_average_basic_2[0,10] FAILED [ 62%] test_average.py::test_average_basic_3[values0-1] PASSED [ 68%] test_average.py::test_average_basic_3[values1-1.5] PASSED [ 75%] test_average.py::test_average_basic_3[values2-0.5] PASSED [ 81%] test_average.py::test_average_basic_3[values3-2.0] PASSED [ 87%] test_average.py::test_average_basic_3[values4-0.5] FAILED [ 93%] test_average.py::test_average_basic_3[values5-0] FAILED [100%] ... ... ... =========================== short test summary info ============================ FAILED test_average.py::test_average_basic_1[values4-0.5] - AssertionError: O... FAILED test_average.py::test_average_basic_2[0,10] - AssertionError: Očekávan... FAILED test_average.py::test_average_basic_3[values4-0.5] - AssertionError: O... FAILED test_average.py::test_average_basic_3[values5-0] - ZeroDivisionError: ... ============ 4 failed, 12 passed, 2 deselected, 5 warnings in 0.09s ============
14. Registrace nových značek v souboru pytest.ini
V předchozích výpisech provedených nástrojem pytest jste si mohli povšimnout zpráv s varováním o neznámých značkách:
test_average.py:23 /home/ptisnovs/src/python/testing-in-python/pytest/average11/test_average.py:23: PytestUnknownMarkWarning: Unknown pytest.mark.smoketest - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html @pytest.mark.smoketest
Varování je zřejmé – nástroj pytest nalezl několik dekorátorů které nezná, takže uživatele upozorňuje na potenciální chyby. Řešení tohoto problému je dvojí. Buď se vytvoří konfigurační soubor nazvaný pytest.ini, který bude tyto značky obsahovat:
[pytest] markers = smoketest thorough
Dvanáctý demonstrační příklad, který tento soubor obsahuje, naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average12/.
15. Registrace nových značek přímo v jednotkovém testu
Alternativně k řešení popsaném v předchozí kapitole je možné přímo v jednotkovém testu vytvořit funkci nazvanou pytest_configure, která je součástí životního cyklu testů, o němž budeme mluvit příště. V této funkci lze do objektu config s konfigurací jednotkových testů přidat další řádek nebo řádky ve formátu:
def pytest_configure(config): """Konfigurace jednotkových testů.""" config.addinivalue_line( "markers", "smoketest: mark test that are performed very smoketest" )
Třináctý demonstrační příklad, který takto upravený test obsahuje, naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average13/.
16. Test fixtures
Povídání o nástroji pytest by v žádném případě nebylo úplné, pokud bychom se nezmínili o takzvaných test fixtures (popravdě nevím, jestli vůbec existuje rozumný překlad tohoto sousloví). Zjednodušeně řečeno se jedná o funkce, které dokážou jednotkovým testům vygenerovat data, připravit kontext, v němž se testy spouští (zajištění připojení do skutečné či mockované databáze…), popř. připravit nějaké pomocné objekty použité v testech. Výhodou fixtures je jejich oddělenost od jednotlivých testů, částečná izolovanost, díky nim lze omezit použití globálních objektů apod.
V tom nejjednodušším případě je test fixture skutečně realizován jednoduchou funkcí, v prvním případě funkcí vracející n-tici s hodnotami použitými v testu:
@pytest.fixture def input_values(): """Vygenerování vstupních hodnot pro jednotkový test.""" return (1, 2, 3, 4, 5)
Druhý test fixture je ještě jednodušší, neboť se jedná o funkci vracející jedinou hodnotu:
@pytest.fixture def expected_result(): """Vygenerování očekávaného výsledku testu.""" return 3
Nejzajímavější je ovšem způsob použití obou výše uvedených test fixtures. Ty totiž můžeme předat přímo zvolenému jednotkovému testu ve formě parametrů – jména parametrů v tomto případě odpovídají jménům funkcí, které test fixtures realizují (ve skutečnosti je inicializace fixtures, jejich zavolání a předání součástí projektu pytest a neprovádí se tedy přímo). Příklad použití je následující:
def test_average_five_values(input_values, expected_result): """Otestování výpočtu průměru.""" result = average(input_values) assert result == expected_result, "Očekávaná hodnota {}, vráceno {}".format(expected_result, result)
Do takto realizovaného jednotkového testu se při spuštění pytest přenese n-tice (1, 2, 3, 4, 5) a taktéž hodnota 3. Jedná se skutečně o jeden z nejjednodušších způsobů použití test fixtures, ovšem příště si uvedeme i složitější a realističtější příklady.
Úplný zdrojový kód s jednotkovými testy vypadá po úpravách následovně:
"""Implementace jednotkových testů.""" import pytest from average import average def pytest_configure(config): """Konfigurace jednotkových testů.""" config.addinivalue_line( "markers", "smoketest: mark test that are performed very smoketest" ) testdata = [ ((1, 1), 1), ((1, 2), 1.5), ((0, 1), 0.5), ((1, 2, 3), 2.0), ((0, 10), 0.5), ] @pytest.mark.smoketest @pytest.mark.parametrize("values,expected", testdata) def test_average_basic_1(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) @pytest.mark.smoketest @pytest.mark.parametrize("values,expected", testdata, ids=["1,1", "1,2", "0,1", "1,2,3", "0,10"]) def test_average_basic_2(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) @pytest.mark.smoketest @pytest.mark.parametrize( "values,expected", [ pytest.param( (1, 1), 1 ), pytest.param( (1, 2), 1.5 ), pytest.param( (0, 1), 0.5 ), pytest.param( (1, 2, 3), 2.0 ), pytest.param( (0, 10), 0.5 ), pytest.param( (), 0 ), ], ) def test_average_basic_3(values, expected): """Otestování výpočtu průměru.""" result = average(values) assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result) @pytest.mark.thorough def test_average_empty_list_1(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(ZeroDivisionError) as excinfo: result = average([]) @pytest.mark.thorough def test_average_empty_list_2(): """Otestování výpočtu průměru pro prázdný vstup.""" with pytest.raises(Exception) as excinfo: result = average([]) # poměrně křehký způsob testování! assert excinfo.type == ZeroDivisionError assert str(excinfo.value) == "float division by zero" @pytest.fixture def input_values(): """Vygenerování vstupních hodnot pro jednotkový test.""" return (1, 2, 3, 4, 5) @pytest.fixture def expected_result(): """Vygenerování očekávaného výsledku testu.""" return 3 def test_average_five_values(input_values, expected_result): """Otestování výpočtu průměru.""" result = average(input_values) assert result == expected_result, "Očekávaná hodnota {}, vráceno {}".format(expected_result, result)
Čtrnáctý a dnes i poslední demonstrační příklad naleznete na adrese https://github.com/tisnik/testing-in-python/blob/master/pytest/average14/.
17. Obsah následující části seriálu
V navazující části seriálu o testování s využitím programovacího jazyka Python se do větších detailů podíváme na různé způsoby použití fixtures. Taktéž se budeme podrobněji zabývat životním cyklem testů, použitím asercí a některými dalšími zajímavými pluginy vyvinutými pro nástroj pytest.
18. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/testing-in-python. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady a jejich části, které naleznete v následující tabulce:
19. Předchozí články s tématem testování (nejenom) v Pythonu
Tématem testování jsme se již na stránkách Rootu několikrát zabývali. Jedná se mj. o následující články:
- Použití Pythonu pro tvorbu testů: od jednotkových testů až po testy UI
https://www.root.cz/clanky/pouziti-pythonu-pro-tvorbu-testu-od-jednotkovych-testu-az-po-testy-ui/ - Použití Pythonu pro tvorbu testů: použití třídy Mock z knihovny unittest.mock
https://www.root.cz/clanky/pouziti-pythonu-pro-tvorbu-testu-pouziti-tridy-mock-z-knihovny-unittest-mock/ - Použití nástroje pytest pro tvorbu jednotkových testů a benchmarků
https://www.root.cz/clanky/pouziti-nastroje-pytest-pro-tvorbu-jednotkovych-testu-a-benchmarku/ - Behavior-driven development v Pythonu s využitím knihovny Behave
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave/ - Behavior-driven development v Pythonu s využitím knihovny Behave (druhá část)
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave-druha-cast/ - Behavior-driven development v Pythonu s využitím knihovny Behave (závěrečná část)
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave-zaverecna-cast/ - Validace datových struktur v Pythonu pomocí knihoven Schemagic a Schema
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-pomoci-knihoven-schemagic-a-schema/ - Validace datových struktur v Pythonu (2. část)
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-2-cast/ - Validace datových struktur v Pythonu (dokončení)
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-dokonceni/ - Univerzální testovací nástroj Robot Framework
https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework/ - Univerzální testovací nástroj Robot Framework a BDD testy
https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework-a-bdd-testy/ - Úvod do problematiky fuzzingu a fuzz testování
https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani/ - Úvod do problematiky fuzzingu a fuzz testování – složení vlastního fuzzeru
https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani-slozeni-vlastniho-fuzzeru/ - Knihovny a moduly usnadňující testování aplikací naprogramovaných v jazyce Clojure
https://www.root.cz/clanky/knihovny-a-moduly-usnadnujici-testovani-aplikaci-naprogramovanych-v-jazyce-clojure/ - Validace dat s využitím knihovny spec v Clojure 1.9.0
https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/ - Testování aplikací naprogramovaných v jazyce Go
https://www.root.cz/clanky/testovani-aplikaci-naprogramovanych-v-jazyce-go/ - Knihovny určené pro tvorbu testů v programovacím jazyce Go
https://www.root.cz/clanky/knihovny-urcene-pro-tvorbu-testu-v-programovacim-jazyce-go/ - Testování aplikací psaných v Go s využitím knihoven Goblin a Frisby
https://www.root.cz/clanky/testovani-aplikaci-psanych-v-go-s-vyuzitim-knihoven-goblin-a-frisby/ - Testování Go aplikací s využitím knihovny GΩmega a frameworku Ginkgo
https://www.root.cz/clanky/testovani-go-aplikaci-s-vyuzitim-knihovny-gomega-mega-a-frameworku-ginkgo/ - Tvorba BDD testů s využitím jazyka Go a nástroje godog
https://www.root.cz/clanky/tvorba-bdd-testu-s-vyuzitim-jazyka-go-a-nastroje-godog/ - Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem
https://www.root.cz/clanky/pouziti-go-pro-automatizaci-prace-s-aplikacemi-s-interaktivnim-prikazovym-radkem/ - Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem (dokončení)
https://www.root.cz/clanky/pouziti-go-pro-automatizaci-prace-s-aplikacemi-s-interaktivnim-prikazovym-radkem-dokonceni/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure (2)
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure-2/
20. Odkazy na Internetu
- pytest 5.4.2 na PyPi
https://pypi.org/project/pytest/ - Awesome Python – testing
https://github.com/vinta/awesome-python#testing - pytest Plugins Compatibility
http://plugincompat.herokuapp.com/ - Selenium (pro Python)
https://pypi.org/project/selenium/ - Getting Started With Testing in Python
https://realpython.com/python-testing/ - unittest.mock — mock object library
https://docs.python.org/3.5/library/unittest.mock.html - mock 2.0.0
https://pypi.python.org/pypi/mock - An Introduction to Mocking in Python
https://www.toptal.com/python/an-introduction-to-mocking-in-python - Mock – Mocking and Testing Library
http://mock.readthedocs.io/en/stable/ - Python Mocking 101: Fake It Before You Make It
https://blog.fugue.co/2016–02–11-python-mocking-101.html - Nauč se Python! – Testování
http://naucse.python.cz/lessons/intro/testing/ - Flexmock (dokumentace)
https://flexmock.readthedocs.io/en/latest/ - Test Fixture (Wikipedia)
https://en.wikipedia.org/wiki/Test_fixture - Mock object (Wikipedia)
https://en.wikipedia.org/wiki/Mock_object - Extrémní programování
https://cs.wikipedia.org/wiki/Extr%C3%A9mn%C3%AD_programov%C3%A1n%C3%AD - Programování řízené testy
https://cs.wikipedia.org/wiki/Programov%C3%A1n%C3%AD_%C5%99%C3%ADzen%C3%A9_testy - Pip (dokumentace)
https://pip.pypa.io/en/stable/ - Tox
https://tox.readthedocs.io/en/latest/ - pytest: helps you write better programs
https://docs.pytest.org/en/latest/ - doctest — Test interactive Python examples
https://docs.python.org/dev/library/doctest.html#module-doctest - unittest — Unit testing framework
https://docs.python.org/dev/library/unittest.html - Python namespaces
https://bytebaker.com/2008/07/30/python-namespaces/ - Namespaces and Scopes
https://www.python-course.eu/namespaces.php - Stránka projektu Robot Framework
https://robotframework.org/ - GitHub repositář Robot Frameworku
https://github.com/robotframework/robotframework - Robot Framework (Wikipedia)
https://en.wikipedia.org/wiki/Robot_Framework - Tutoriál Robot Frameworku
http://www.robotframeworktutorial.com/ - Robot Framework Documentation
https://robotframework.org/robotframework/ - Robot Framework Introduction
https://blog.testproject.io/2016/11/22/robot-framework-introduction/ - robotframework 3.1.2 na PyPi
https://pypi.org/project/robotframework/ - Robot Framework demo (GitHub)
https://github.com/robotframework/RobotDemo - Robot Framework web testing demo using SeleniumLibrary
https://github.com/robotframework/WebDemo - Robot Framework for Mobile Test Automation Demo
https://www.youtube.com/watch?v=06LsU08slP8 - Gherkin
https://cucumber.io/docs/gherkin/ - Selenium
https://selenium.dev/ - SeleniumLibrary
https://robotframework.org/ - The Practical Test Pyramid
https://martinfowler.com/articles/practical-test-pyramid.html - Acceptance Tests and the Testing Pyramid
http://www.blog.acceptancetestdrivendevelopment.com/acceptance-tests-and-the-testing-pyramid/ - Tab-separated values
https://en.wikipedia.org/wiki/Tab-separated_values - A quick guide about Python implementations
https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321 - radamsa
https://gitlab.com/akihe/radamsa - Fuzzing (Wikipedia)
https://en.wikipedia.org/wiki/Fuzzing - american fuzzy lop
http://lcamtuf.coredump.cx/afl/ - Fuzzing: the new unit testing
https://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/fuzzing.slide#1 - Corpus for github.com/dvyukov/go-fuzz examples
https://github.com/dvyukov/go-fuzz-corpus - AFL – QuickStartGuide.txt
https://github.com/google/AFL/blob/master/docs/QuickStartGuide.txt - Introduction to Fuzzing in Python with AFL
https://alexgaynor.net/2015/apr/13/introduction-to-fuzzing-in-python-with-afl/ - Writing a Simple Fuzzer in Python
https://jmcph4.github.io/2018/01/19/writing-a-simple-fuzzer-in-python/ - How to Fuzz Go Code with go-fuzz (Continuously)
https://fuzzit.dev/2019/10/02/how-to-fuzz-go-code-with-go-fuzz-continuously/ - Golang Fuzzing: A go-fuzz Tutorial and Example
http://networkbit.ch/golang-fuzzing/ - Fuzzing Python Modules
https://stackoverflow.com/questions/20749026/fuzzing-python-modules - 0×3 Python Tutorial: Fuzzer
http://www.primalsecurity.net/0×3-python-tutorial-fuzzer/ - fuzzing na PyPi
https://pypi.org/project/fuzzing/ - Fuzzing 0.3.2 documentation
https://fuzzing.readthedocs.io/en/latest/ - Randomized testing for Go
https://github.com/dvyukov/go-fuzz - HTTP/2 fuzzer written in Golang
https://github.com/c0nrad/http2fuzz - Ffuf (Fuzz Faster U Fool) – An Open Source Fast Web Fuzzing Tool
https://hacknews.co/hacking-tools/20191208/ffuf-fuzz-faster-u-fool-an-open-source-fast-web-fuzzing-tool.html - Continuous Fuzzing Made Simple
https://fuzzit.dev/ - Halt and Catch Fire
https://en.wikipedia.org/wiki/Halt_and_Catch_Fire#Intel_x86 - Random testing
https://en.wikipedia.org/wiki/Random_testing - Monkey testing
https://en.wikipedia.org/wiki/Monkey_testing - Fuzzing for Software Security Testing and Quality Assurance, Second Edition
https://books.google.at/books?id=tKN5DwAAQBAJ&pg=PR15&lpg=PR15&q=%22I+settled+on+the+term+fuzz%22&redir_esc=y&hl=de#v=onepage&q=%22I%20settled%20on%20the%20term%20fuzz%22&f=false - libFuzzer – a library for coverage-guided fuzz testing
https://llvm.org/docs/LibFuzzer.html - fuzzy-swagger na PyPi
https://pypi.org/project/fuzzy-swagger/ - fuzzy-swagger na GitHubu
https://github.com/namuan/fuzzy-swagger - Fuzz testing tools for Python
https://wiki.python.org/moin/PythonTestingToolsTaxonomy#Fuzz_Testing_Tools - A curated list of awesome Go frameworks, libraries and software
https://github.com/avelino/awesome-go - gofuzz: a library for populating go objects with random values
https://github.com/google/gofuzz - tavor: A generic fuzzing and delta-debugging framework
https://github.com/zimmski/tavor - hypothesis na GitHubu
https://github.com/HypothesisWorks/hypothesis - Hypothesis: Test faster, fix more
https://hypothesis.works/ - Hypothesis
https://hypothesis.works/articles/intro/ - What is Hypothesis?
https://hypothesis.works/articles/what-is-hypothesis/ - Databáze CVE
https://www.cvedetails.com/ - Fuzz test Python modules with libFuzzer
https://github.com/eerimoq/pyfuzzer - Taof – The art of fuzzing
https://sourceforge.net/projects/taof/ - JQF + Zest: Coverage-guided semantic fuzzing for Java
https://github.com/rohanpadhye/jqf - http2fuzz
https://github.com/c0nrad/http2fuzz - Demystifying hypothesis testing with simple Python examples
https://towardsdatascience.com/demystifying-hypothesis-testing-with-simple-python-examples-4997ad3c5294 - Testování
http://voho.eu/wiki/testovani/ - Unit testing (Wikipedia.en)
https://en.wikipedia.org/wiki/Unit_testing - Unit testing (Wikipedia.cz)
https://cs.wikipedia.org/wiki/Unit_testing - Unit Test vs Integration Test
https://www.youtube.com/watch?v=0GypdsJulKE - TestDouble
https://martinfowler.com/bliki/TestDouble.html - Test Double
http://xunitpatterns.com/Test%20Double.html - Test-driven development (Wikipedia)
https://en.wikipedia.org/wiki/Test-driven_development - Acceptance test–driven development
https://en.wikipedia.org/wiki/Acceptance_test%E2%80%93driven_development - Gauge
https://gauge.org/ - Gauge (software)
https://en.wikipedia.org/wiki/Gauge_(software) - PYPL PopularitY of Programming Language
https://pypl.github.io/PYPL.html - Testing is Good. Pyramids are Bad. Ice Cream Cones are the Worst
https://medium.com/@fistsOfReason/testing-is-good-pyramids-are-bad-ice-cream-cones-are-the-worst-ad94b9b2f05f - Články a zprávičky věnující se Pythonu
https://www.root.cz/n/python/ - PythonTestingToolsTaxonomy
https://wiki.python.org/moin/PythonTestingToolsTaxonomy - Top 6 BEST Python Testing Frameworks [Updated 2020 List]
https://www.softwaretestinghelp.com/python-testing-frameworks/ - pytest-print 0.1.3
https://pypi.org/project/pytest-print/ - pytest fixtures: explicit, modular, scalable
https://docs.pytest.org/en/latest/fixture.html - PyTest Tutorial: What is, Install, Fixture, Assertions
https://www.guru99.com/pytest-tutorial.html - Pytest – Fixtures
https://www.tutorialspoint.com/pytest/pytest_fixtures.htm - Marking test functions with attributes
https://docs.pytest.org/en/latest/mark.html