Obsah
1. Použití nástroje pytest pro tvorbu jednotkových testů
2. Pořadí předávání mock objektů do funkce s dekorátorem @patch
3. Nástroj pytest – nejpřímější způsob spouštění a vyhodnocování jednotkových testů
4. Doplňkové moduly pro nástroj pytest
5. Jednoduchý projekt ukazující základní možnosti nástroje pytest
6. Spuštění nástroje pytest s vyhodnocením výsledků
7. Instalace a použití nového pluginu
8. Ladění v případě, že dochází k problémům při inicializaci pluginu
9. Zjištění pokrytí zdrojového kódu jednotkovými testy
10. Podrobnější výsledky pokrytí zdrojového kódu jednotkovými testy
11. Doplnění jedné chybějící větve do jednotkových testů
12. Využití komentáře # pragma: no cover
13. Skutečné plné pokrytí kódu testy
14. Benchmarky spouštěné v rámci jednotkových testů
16. Dvě verze algoritmu s jejich porovnáním
17. Porovnání výsledků benchmarku
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ů
V dnešní části seriálu věnovaného testování s využitím programovacího jazyka Python nejprve dokončíme popis dekorátoru @patch z knihovny unittest.mock a posléze se budeme věnovat známému nástroji se jménem pytest, který se používá mj. i pro tvorbu jednotkových testů. Tento nástroj je užitečný i díky tomu, že pro něj vzniklo velké množství přídavných modulů, mj. i modul zjišťující pokrytí zdrojového kódu jednotkovými testy (tedy určením, které příkazy jsou skutečně zavolány a které nikoli). Ve druhé části článku si ukážeme, jakým způsobem je možné v rámci testovacího frameworku pytest spouštět benchmarky a vyhodnocovat jejich výsledky s případným porovnáním výsledků (například po provedeném refaktoringu).
2. Pořadí předávání mock objektů do funkce s dekorátorem @patch
V závěrečné kapitole věnované mockování s využitím knihovny unittest.mock se ještě musíme zmínit o další možná poněkud matoucí vlastnosti dekorátoru @patch. Pořadí předání reference na mockované funkce je totiž přesně opačné, než je pořadí zápisu dekorátorů! Jinými slovy – dekorátor zapsaný nejblíže definici funkce (tedy nejníže) odpovídá prvnímu parametru, dekorátor o řádek výše parametru druhému atd.
Tuto vlastnost si můžeme velmi snadno odzkoušet. Test se dvěma mocky může začínat následovně:
@patch("module1.f1", name="f1", return_value=1) @patch("module1.f2", name="f2", return_value=2) def test3(mocked_f2, mocked_f1): ... ... ...
Test se třemi mocky může vypadat například takto:
@patch("module1.f1", name="f1", return_value=1) @patch("module1.f2", name="f2", return_value=2) @patch("module1.f3", name="f3", return_value=3) def test4(mocked_f3, mocked_f2, mocked_f1): ... ... ...
Tímto způsobem deklarované mocky jsou součástí demonstračního příkladu dostupného na adrese https://github.com/tisnik/testing-in-python/tree/master/unittest_mock/mock-testC, jehož zdrojový kód je uveden níže.
Pokud poslední příklad spustíte příkazem ./test, měly by se na standardní výstup vypsat následující řádky:
*** test1 *** compute returns: 14 *** test2 *** compute returns: 130 mocked function 'f1' called: True calls: [call(10)] *** test3 *** compute returns: 9 mocked function 'f1' called: True calls: [call(1)] mocked function 'f2' called: True calls: [call(2)] *** test4 *** compute returns: 0 mocked function 'f1' called: True calls: [call(100)] mocked function 'f2' called: True calls: [call(200)] mocked function 'f3' called: True calls: [call(300)]
Všechny skripty, které jsou součástí tohoto demonstračního příkladu:
main.py:
#!/usr/bin/env python3 """Vstupní bod do testované aplikace.""" from module1 import * if __name__ == '__main__': print(compute(1, 2, 3))
module1.py:
from module2 import * def compute(x, y, z): return f1(x) + f2(y) + f3(z)
module2.py:
def f1(x): return x def f2(x): return 2 * x def f3(x): return 3 * x
test.py:
"""Implementace jednotkových testů.""" from unittest.mock import * from module1 import * def test1(): print("*** test1 ***") value = compute(1, 2, 3) print("compute returns: {v}".format(v=value)) def mock_call_info(mocked_function): print("mocked function '{n}' called: {c}".format(n=mocked_function._mock_name, c=mocked_function.called)) print("calls: ", mocked_function.mock_calls) print() @patch("module1.f1", name="f1", return_value=0) def test2(mocked_f1): print("*** test1 ***") value = compute(10, 20, 30) print("compute returns: {v}".format(v=value)) mock_call_info(mocked_f1) @patch("module1.f1", name="f1", return_value=0) @patch("module1.f2", name="f2", return_value=0) def test3(mocked_f2, mocked_f1): print("*** test3 ***") value = compute(1, 2, 3) print("compute returns: {v}".format(v=value)) mock_call_info(mocked_f1) mock_call_info(mocked_f2) @patch("module1.f1", name="f1", return_value=0) @patch("module1.f2", name="f2", return_value=0) @patch("module1.f3", name="f3", return_value=0) def test4(mocked_f3, mocked_f2, mocked_f1): print("*** test4 ***") value = compute(100, 200, 300) print("compute returns: {v}".format(v=value)) mock_call_info(mocked_f1) mock_call_info(mocked_f2) mock_call_info(mocked_f3) if __name__ == '__main__': test1() print() test2() print() test3() print() test4() print()
3. Nástroj pytest – nejpřímější způsob spouštění a vyhodnocování jednotkových testů
Již v předchozích dvou článcích jsme si řekli že pro programovací jazyk Python existuje hned několik knihoven a frameworků určených pro spouštění a vyhodnocování jednotkových testů. Jedním z nejznámějších a nejpoužívanějších frameworků tohoto typu je pytest. Ve skutečnosti se pytest nemusí používat pouze pro zápis jednotkových testů, ale lze ho využít i pro testy komponent, fuzzy testing atd. V dnešním článku si ukážeme pouze základní možnosti použití tohoto nástroje, zejména pak:
- samotné spouštění jednotkových testů
- vyhodnocení výsledků jednotkových testů
- zjištění pokrytí zdrojového kódu testy
- spouštění benchmarků
4. Doplňkové moduly pro nástroj pytest
Vzhledem k tomu, že je pytest v současnosti považován za de facto standard v oblasti jednotkových testů pro programovací jazyk Python, vzniklo pro něj několik set(!) doplňkových modulů určených pro různé použití. Základní informace o těchto modulech (odkaz na stránku s dalšími informacemi o modulu a informace o kompatibilitě s různými verzemi Pythonu 3) jsou vypsány v následující tabulce:
5. Jednoduchý projekt ukazující základní možnosti nástroje pytest
Nyní se podívejme na zdrojový kód velmi jednoduchého projektu, na němž si postupně ukážeme jednotlivé možnosti poskytované nástrojem pytest. Základní skript se jmenuje primes.py a obsahuje implementaci výpočtu prvočísel s využitím starodávného algoritmu známého pod jménem Eratosthenovo síto. Konkrétní poměrně rychlá implementace byla převzata ze známého serveru Rosetta Code:
"""Výpočet seznamu prvočísel až do zadaného limitu.""" # originální kód lze nalézt na adrese: # http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Odds-only_version_of_the_array_sieve_above def primes2(limit): """Výpočet seznamu prvočísel až do zadaného limitu.""" # okrajový případ if limit < 2: return [] # druhý případ - 2 je speciálním prvočíslem if limit < 3: return [2] lmtbf = (limit - 3) // 2 # naplnění tabulky, která se bude prosívat buf = [True] * (lmtbf + 1) # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel return [2] + [i + i + 3 for i, v in enumerate(buf) if v]
Skript main.py je spustitelný – pouze vypočte a zobrazí prvočísla v rozsahu 0..100:
#!/usr/bin/env python3 """Vstupní bod do testované aplikace.""" from primes import * if __name__ == '__main__': # pouze se ujistíme, že lze spustit funkci primes2 print(primes2(100))
A konečně soubor test_primes.py obsahuje prozatím velmi jednoduchou implementaci jednotkových testů. Povšimněte si, že se v testech přímo používá klíčové slovo assert – pokud funkce neskončí s výjimkou, je test vyhodnocen jako pass, v odlišném případě je hlášena chyba:
"""Implementace jednotkových testů.""" from primes import primes2 def test_primes_10(): """Otestování výpočtu seznamu prvočísel až do limitu 10.""" # získat seznam prvočísel až do limitu 10 p = primes2(10) # testy lze dále rozšiřovat assert 2 in p assert 10 not in p
6. Spuštění nástroje pytest s vyhodnocením výsledků
V adresáři s demonstračním projektem postačuje spustit příkaz pytest. Nejprve si vyzkoušíme, co se stane ve chvíli, kdy nepoužijeme žádné další parametry:
$ pytest
Měla by se zobrazit (mj.) následující tabulka s celkovými výsledky testů:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/primes1 plugins: voluptuous-1.0.2, cov-2.5.1 collected 1 item test_primes.py . [100%] ============================== 1 passed in 0.02s ===============================
Pokud budeme chtít zobrazit, které konkrétní testy se spouští, pomůže nám „ukecaný“ (verbose) režim:
$ pytest -v
Ve výsledcích se nyní objeví informace o jednotlivých funkcích a metodách s jednotkovými testy, které jsou spouštěny:
============================= 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 rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/primes1 plugins: voluptuous-1.0.2, cov-2.5.1 collected 1 item test_primes.py::test_primes_10 PASSED [100%] ============================== 1 passed in 0.02s ===============================

Obrázek 1: Zobrazení na terminálu s podporou barevného výstupu.
Nyní algoritmus výpočtu schválně poškodíme – vynecháme přidání čísla 2 (které je jediným sudým prvočíslem a tudíž se s ním pracuje odlišně):
# vytvoření seznamu prvočísel return [i + i + 3 for i, v in enumerate(buf) if v]
Jednotkové testy by nyní měly podle očekávání zhavarovat:
$ pytest -v =================================== FAILURES =================================== ________________________________ test_primes_10 ________________________________ def test_primes_10(): """Otestování výpočtu seznamu prvočísel až do limitu 10.""" # získat seznam prvočísel až do limitu 10 p = primes2(10) # testy lze dále rozšiřovat > assert 2 in p E assert 2 in [3, 5, 7] test_primes.py:11: AssertionError =========================== short test summary info ============================ FAILED test_primes.py::test_primes_10 - assert 2 in [3, 5, 7] ============================== 1 failed in 0.08s ===============================

Obrázek 2: Zobrazení na terminálu s podporou barevného výstupu.
Další možností je spuštění s parametrem -rA, které pod testy zobrazí ještě krátké celkové zhodnocení, což je výhodné zejména u projektů s mnoha stovkami testů:
$ pytest -rA ============================= 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/primes1 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collected 1 item test_primes.py . [100%] ==================================== PASSES ==================================== =========================== short test summary info ============================ PASSED test_primes.py::test_primes_10 ============================== 1 passed in 0.02s ===============================
7. Instalace a použití nového pluginu
Pluginy, například ty zmíněné ve čtvrté kapitole, se instalují stejným způsobem, jako jakýkoli jiný balíček určený pro programovací jazyk Python. Pokud například používáte nástroj pip nebo pip3 (podle aktuálního nastavení operačního systému), může instalace dále popsaného pluginu pytest-benchmark vypadat následovně:
$ pip3 install --user pytest-benchmark
8. Ladění v případě, že dochází k problémům při inicializaci pluginu
V případě, že při načítání pluginů nebo při hledání testů dochází k problémům (většinou dosti záhadným), může pomoci spustit nástroj pytest s přepínačem –trace-config, tedy například následujícím způsobem:
$ pytest --trace-config
Výsledek může vypadat například následovně (povšimněte si, že většina balíčků byla nainstalována pouze lokálně):
PLUGIN registered: <_pytest.config.PytestPluginManager object at 0x7f909fbba630> PLUGIN registered: <_pytest.config.Config object at 0x7f909f3c4e48> PLUGIN registered: <module '_pytest.mark' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/mark.py'> PLUGIN registered: <module '_pytest.main' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/main.py'> PLUGIN registered: <module '_pytest.terminal' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/terminal.py'> PLUGIN registered: <module '_pytest.runner' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/runner.py'> PLUGIN registered: <module '_pytest.python' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/python.py'> PLUGIN registered: <module '_pytest.pdb' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/pdb.py'> PLUGIN registered: <module '_pytest.unittest' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/unittest.py'> PLUGIN registered: <module '_pytest.capture' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/capture.py'> PLUGIN registered: <module '_pytest.skipping' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/skipping.py'> PLUGIN registered: <module '_pytest.tmpdir' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/tmpdir.py'> PLUGIN registered: <module '_pytest.monkeypatch' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/monkeypatch.py'> PLUGIN registered: <module '_pytest.recwarn' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/recwarn.py'> PLUGIN registered: <module '_pytest.pastebin' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/pastebin.py'> PLUGIN registered: <module '_pytest.helpconfig' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/helpconfig.py'> PLUGIN registered: <module '_pytest.nose' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/nose.py'> PLUGIN registered: <module '_pytest.assertion' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/assertion/__init__.py'> PLUGIN registered: <module '_pytest.genscript' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/genscript.py'> PLUGIN registered: <module '_pytest.junitxml' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/junitxml.py'> PLUGIN registered: <module '_pytest.resultlog' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/resultlog.py'> PLUGIN registered: <module '_pytest.doctest' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/doctest.py'> PLUGIN registered: <module '_pytest.cacheprovider' from '/home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/cacheprovider.py'> PLUGIN registered: <module 'pytest_voluptuous.plugin' from '/home/ptisnovs/.local/lib/python3.6/site-packages/pytest_voluptuous/plugin.py'> PLUGIN registered: <module 'pytest_cov.plugin' from '/usr/lib/python3.6/site-packages/pytest_cov/plugin.py'> PLUGIN registered: <_pytest.capture.CaptureManager object at 0x7f909aee1390> PLUGIN registered: <Session 'pytest'> PLUGIN registered: <_pytest.cacheprovider.LFPlugin object at 0x7f909aef1da0> PLUGIN registered: <_pytest.terminal.TerminalReporter object at 0x7f909ac7ab38> PLUGIN registered: <_pytest.python.FixtureManager object at 0x7f909ac2bcf8> ============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-2.9.2, py-1.5.2, pluggy-0.3.1 using: pytest-2.9.2 pylib-1.5.2 setuptools registered plugins: pytest-voluptuous-1.0.2 at /home/ptisnovs/.local/lib/python3.6/site-packages/pytest_voluptuous/plugin.py pytest-cov-2.5.1 at /usr/lib/python3.6/site-packages/pytest_cov/plugin.py active plugins: 140259131893296 : <_pytest.config.PytestPluginManager object at 0x7f909fbba630> pytestconfig : <_pytest.config.Config object at 0x7f909f3c4e48> mark : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/mark.py main : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/main.py terminal : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/terminal.py runner : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/runner.py python : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/python.py pdb : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/pdb.py unittest : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/unittest.py capture : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/capture.py skipping : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/skipping.py tmpdir : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/tmpdir.py monkeypatch : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/monkeypatch.py recwarn : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/recwarn.py pastebin : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/pastebin.py helpconfig : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/helpconfig.py nose : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/nose.py assertion : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/assertion/__init__.py genscript : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/genscript.py junitxml : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/junitxml.py resultlog : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/resultlog.py doctest : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/doctest.py cacheprovider : /home/ptisnovs/.local/lib/python3.6/site-packages/_pytest/cacheprovider.py pytest_voluptuous : /home/ptisnovs/.local/lib/python3.6/site-packages/pytest_voluptuous/plugin.py pytest_cov : /usr/lib/python3.6/site-packages/pytest_cov/plugin.py capturemanager : <_pytest.capture.CaptureManager object at 0x7f909aee1390> session : <Session 'pytest'> lfplugin : <_pytest.cacheprovider.LFPlugin object at 0x7f909aef1da0> terminalreporter : <_pytest.terminal.TerminalReporter object at 0x7f909ac7ab38> funcmanage : <_pytest.python.FixtureManager object at 0x7f909ac2bcf8> rootdir: /home/ptisnovs/src/python/testing-in-python/pytest, inifile: plugins: voluptuous-1.0.2, cov-2.5.1
9. Zjištění pokrytí zdrojového kódu jednotkovými testy
Velmi užitečným pluginem pro nástroj pytest je plugin nazvaný pytest-cov (lze pochopitelně nalézt na PyPi). Tento plugin nainstalujeme jednoduše:
$ pip3 install --user pytest-cov
Po úspěšné instalaci můžeme přidat přepínač –cov, jehož hodnotou je jméno balíčku, popř. modulu(ů), které jsou testovány, tj. ty zdrojové kódy, u kterých potřebujeme znát pokrytí jednotkovými testy. Nejprve provedeme spuštění pro všechny skripty v aktuálním balíčku (zde adresáři):
pytest --cov=.
Ve výsledku s procenty pokrytí jednotkovými testy se v tomto případě objeví všechny skripty:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/primes1 plugins: voluptuous-1.0.2, cov-2.5.1 collected 1 item test_primes.py . [100%] ----------- coverage: platform linux, python 3.6.6-final-0 ----------- Name Stmts Miss Cover ------------------------------------ main.py 4 4 0% primes.py 13 2 85% test_primes.py 5 0 100% ------------------------------------ TOTAL 22 6 73% ============================== 1 passed in 0.04s ===============================
Lepší a korektnější je specifikovat pouze jediný skript (bez uvedení koncovky):
$ pytest --cov=primes
Nyní se ve výsledku objeví skutečně pouze vlastní testovaný kód a nic dalšího:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/primes1 plugins: voluptuous-1.0.2, cov-2.5.1 collected 1 item test_primes.py . [100%] ----------- coverage: platform linux, python 3.6.6-final-0 ----------- Name Stmts Miss Cover ------------------------------- primes.py 13 2 85% ============================== 1 passed in 0.04s ===============================
10. Podrobnější výsledky pokrytí zdrojového kódu jednotkovými testy
Informace o tom, kolik procent zdrojového kódu je pokryto (skutečně vykonáno) v jednotkových testech, je sice užitečná, ovšem ještě lepší budou podrobnější informace o těch řádcích ve zdrojovém kódu, které naopak vyhodnoceny nebyly. I tyto informace je pochopitelně možné získat, a to velmi snadným způsobem:
$ pytest --cov=primes --cov-report term-missing
Nyní by se ve výpisu měly objevit i jednotlivé řádky, které nebyly v rámci jednotkových testů zavolány:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/primes1 plugins: voluptuous-1.0.2, cov-2.5.1 collected 1 item test_primes.py . [100%] ----------- coverage: platform linux, python 3.6.6-final-0 ----------- Name Stmts Miss Cover Missing ----------------------------------------- primes.py 13 2 85% 11, 15 ============================== 1 passed in 0.04s ===============================
Dokonce je možné si nechat vygenerovat HTML stránku (a několik dalších souborů s kaskádovými styly a JavaScriptem) s podrobnějšími informacemi o pokrytí zdrojových kódů testy:
$ pytest --cov=primes --cov-report html

Obrázek 3: Podrobnější výsledky s pokrytím zdrojového kódu jednotkovými testy. Nyní je zcela jasně patrné, které části je potřeba více otestovat.
11. Doplnění jedné chybějící větve do jednotkových testů
Z předchozího výsledku testů je zřejmé, že nám chybí doplnit testy pro dvě podmínky vyhodnocované na začátku výpočtu sekvence prvočísel. Pokusme se tedy doplnit test, zda se v prázdném rozsahu 0..0 nenachází žádné prvočíslo. Nový test je implementován ve funkci pojmenované test_primes0:
"""Implementace jednotkových testů.""" from primes import primes2 def test_primes_10(): """Otestování výpočtu seznamu prvočísel až do limitu 10.""" # získat seznam prvočísel až do limitu 10 p = primes2(10) # testy lze dále rozšiřovat assert 2 in p assert 10 not in p def test_primes_0(): """Otestování výpočtu seznamu prvočísel do limitu 0.""" p = primes2(0) # otestujeme, zda je sekvence prázdná (není zcela přesné) assert not p
Nové spuštění testů by mělo odhalit, že z celého výpočtu zůstal neotestován jen jediný programový řádek:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/primes2 plugins: voluptuous-1.0.2, cov-2.5.1 collected 2 items test_primes.py .. [100%] ----------- coverage: platform linux, python 3.6.6-final-0 ----------- Name Stmts Miss Cover Missing ----------------------------------------- primes.py 13 1 92% 15 ============================== 2 passed in 0.04s ===============================
Ještě lépe jsou výsledky patrné při pohledu na vygenerovanou HTML stránku:

Obrázek 4: Podrobnější výsledky s pokrytím zdrojového kódu jednotkovými testy. Vidíme, že nepokrytý zůstal pouze jediný řádek v testovaném zdrojovém kódu.
12. Využití komentáře # pragma: no cover
S využitím komentáře „# pragma: no cover“ je možné označit ty řádky ve zdrojovém kódu, které budou při výpočtu pokrytí zdrojových kódů ignorovány. Lze označit jednotlivé řádky, bloky (to již naznačuje lenost testera :-), funkce či dokonce celé třídy. Pochopitelně se jedná o dvousečnou a často zneužívanou vlastnost:
"""Výpočet seznamu prvočísel až do zadaného limitu.""" # originální kód lze nalézt na adrese: # http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Odds-only_version_of_the_array_sieve_above def primes2(limit): """Výpočet seznamu prvočísel až do zadaného limitu.""" # okrajový případ if limit < 2: # pragma: no cover return [] # druhý případ - 2 je speciálním prvočíslem if limit < 3: # pragma: no cover return [2] lmtbf = (limit - 3) // 2 # naplnění tabulky, která se bude prosívat buf = [True] * (lmtbf + 1) # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel return [2] + [i + i + 3 for i, v in enumerate(buf) if v]
Nové výsledky, nyní s (virtuálně) stoprocentním pokrytím:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/primes3 plugins: voluptuous-1.0.2, cov-2.5.1 collected 2 items test_primes.py .. [100%] ----------- coverage: platform linux, python 3.6.6-final-0 ----------- Name Stmts Miss Cover Missing ----------------------------------------- primes.py 9 0 100% ============================== 2 passed in 0.04s ===============================

Obrázek 5: Pohled na výsledky exportované do HTML prozradí, jak je to se stoprocentním pokrytím ve skutečnosti.
13. Skutečné plné pokrytí kódu testy
Samozřejmě je – alespoň v našem jednoduchém projektu – možné i bez výše uvedeného triku dosáhnout stoprocentního pokrytí jednotkovými testy. Ani není nutné používat mocky či další triky; pouze postačuje jednotkové testy rozšířit a zjistit, jak se testovaná funkce chová pro vstupní hodnoty 0, 1 a 2:
"""Implementace jednotkových testů.""" from primes import primes2 def test_primes_10(): """Otestování výpočtu seznamu prvočísel až do limitu 10.""" # získat seznam prvočísel až do limitu 10 p = primes2(10) # testy lze dále rozšiřovat assert 2 in p assert 10 not in p def test_primes_0(): """Otestování výpočtu seznamu prvočísel do limitu 0.""" p = primes2(0) # otestujeme, zda je sekvence prázdná (není zcela přesné) assert not p def test_primes_2(): """Otestování výpočtu seznamu prvočísel do limitu 2.""" p = primes2(2) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert 2 in p assert p == [2]
Výsledky nyní plně odpovídají naší snaze:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 rootdir: /home/ptisnovs/src/python/testing-in-python/pytest/primes4 plugins: voluptuous-1.0.2, cov-2.5.1 collected 3 items test_primes.py ... [100%] ----------- coverage: platform linux, python 3.6.6-final-0 ----------- Name Stmts Miss Cover Missing ----------------------------------------- primes.py 13 0 100% ============================== 3 passed in 0.04s ===============================

Obrázek 6: Stoprocentní pokrytí zdrojového kódu jednotkovými testy.
14. Benchmarky spouštěné v rámci jednotkových testů
V rámci testovacího frameworku pytest je možné spouštět i benchmarky s případným vyhodnocením jejich výsledků, porovnáním několika benchmarků, vygenerováním grafu s výsledky atd. Aby byly benchmarky použitelné, je nutné nainstalovat nový plugin nazvaný pytest-benchmark, což se provede například takto (opět pouze pro aktuálně přihlášeného uživatele):
$ pip3 install --user pytest-benchmark
Průběh instalace:
... ... ... 100% |████████████████████████████████| 51kB 806kB/s Collecting pytest>=3.8 (from pytest-benchmark) Downloading https://files.pythonhosted.org/packages/6c/f9/9f2b6c672c8f8bb87a4c1bd52c1b57213627b035305aad745d015b2a62ae/pytest-5.4.2-py3-none-any.whl (247kB) 100% |████████████████████████████████| 256kB 1.5MB/s Collecting py-cpuinfo (from pytest-benchmark) Downloading https://files.pythonhosted.org/packages/42/60/63f28a5401da733043abe7053e7d9591491b4784c4f87c339bf51215aa0a/py-cpuinfo-5.0.0.tar.gz (82kB) 100% |████████████████████████████████| 92kB 3.1MB/s Collecting more-itertools>=4.0.0 (from pytest>=3.8->pytest-benchmark) Downloading https://files.pythonhosted.org/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB) 100% |████████████████████████████████| 51kB 2.2MB/s Requirement already satisfied: py>=1.5.0 in /usr/lib/python3.6/site-packages (from pytest>=3.8->pytest-benchmark) Collecting importlib-metadata>=0.12; python_version < "3.8" (from pytest>=3.8->pytest-benchmark) Downloading https://files.pythonhosted.org/packages/ad/e4/891bfcaf868ccabc619942f27940c77a8a4b45fd8367098955bb7e152fb1/importlib_metadata-1.6.0-py2.py3-none-any.whl Collecting pluggy<1.0,>=0.12 (from pytest>=3.8->pytest-benchmark) Downloading https://files.pythonhosted.org/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl Requirement already satisfied: attrs>=17.4.0 in /usr/lib/python3.6/site-packages (from pytest>=3.8->pytest-benchmark) Requirement already satisfied: wcwidth in ./.local/lib/python3.6/site-packages (from pytest>=3.8->pytest-benchmark) Collecting packaging (from pytest>=3.8->pytest-benchmark) Downloading https://files.pythonhosted.org/packages/62/0a/34641d2bf5c917c96db0ded85ae4da25b6cd922d6b794648d4e7e07c88e5/packaging-20.3-py2.py3-none-any.whl Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < "3.8"->pytest>=3.8->pytest-benchmark) Downloading https://files.pythonhosted.org/packages/b2/34/bfcb43cc0ba81f527bc4f40ef41ba2ff4080e047acb0586b56b3d017ace4/zipp-3.1.0-py3-none-any.whl Requirement already satisfied: pyparsing>=2.0.2 in /usr/lib/python3.6/site-packages (from packaging->pytest>=3.8->pytest-benchmark) Requirement already satisfied: six in ./.local/lib/python3.6/site-packages (from packaging->pytest>=3.8->pytest-benchmark) Installing collected packages: more-itertools, zipp, importlib-metadata, pluggy, packaging, pytest, py-cpuinfo, pytest-benchmark Found existing installation: pytest 2.9.2 Uninstalling pytest-2.9.2: Successfully uninstalled pytest-2.9.2 Running setup.py install for py-cpuinfo ... done Successfully installed importlib-metadata-1.6.0 more-itertools-8.2.0 packaging-20.3 pluggy-0.13.1 py-cpuinfo-5.0.0 pytest-5.4.2 pytest-benchmark-3.2.3 zipp-3.1.0
Pokud nyní spustíte již existující jednotkové testy, bude jedinou viditelnou změnou nový řádek informující o pluginu benchmark a pochopitelně i jméno (a verze) tohoto pluginu vypsaná v rámci sekce „plugins“. Způsob spouštění testů i jejich vyhodnocení prozatím nebudou žádným způsobem pozměněny:
============================= 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/primes4 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collected 3 items test_primes.py ... [100%] ----------- coverage: platform linux, python 3.6.6-final-0 ----------- Name Stmts Miss Cover Missing ----------------------------------------- primes.py 13 0 100% ============================== 3 passed in 0.04s ===============================
15. Jednoduchý benchmark
„fast functions are no good if they return incorrect results“
Napsání benchmarku je ve skutečnosti snadné. Ukážeme si to na příkladu následujícího testu, který spočítá prvočísla v rozsahu 0 až 10:
def test_primes_10(): """Otestování výpočtu seznamu prvočísel až do limitu 10.""" # získat seznam prvočísel až do limitu 10 p = primes2(10) # testy lze dále rozšiřovat assert 2 in p assert 10 not in p
Převod jednotkového testu na benchmark se provede jen s využitím dvou změn:
- Přidáním parametru benchmark do funkce představující jednotkový test
- Změnou volání primes2(10) za benchmark(primes2, 10)
Výsledek může vypadat takto:
def test_primes_10(benchmark): """Otestování výpočtu seznamu prvočísel až do limitu 10.""" # získat seznam prvočísel až do limitu 10 p = benchmark(primes2, 10) # testy lze dále rozšiřovat assert 2 in p assert 10 not in p
Podobně lze přepsat i další testy:
"""Implementace jednotkových testů.""" from primes import primes2 def test_primes_10(benchmark): """Otestování výpočtu seznamu prvočísel až do limitu 10.""" # získat seznam prvočísel až do limitu 10 p = benchmark(primes2, 10) # testy lze dále rozšiřovat assert 2 in p assert 10 not in p def test_primes_100000(benchmark): """Otestování výpočtu seznamu prvočísel až do limitu 100000.""" # získat seznam prvočísel až do limitu 100000 p = benchmark(primes2, 100000) # testy lze dále rozšiřovat assert 2 in p assert 10 not in p # hodnoty získány ze seznamu: # https://primes.utm.edu/lists/small/10000.txt assert 99989 in p assert 99991 in p def test_primes_0(benchmark): """Otestování výpočtu seznamu prvočísel do limitu 0.""" p = benchmark(primes2, 0) # otestujeme, zda je sekvence prázdná (není zcela přesné) assert not p def test_primes_2(benchmark): """Otestování výpočtu seznamu prvočísel do limitu 2.""" p = benchmark(primes2, 2) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert 2 in p assert p == [2]
Nyní bude spuštění testů vypadat odlišně, protože se ukážou i výsledky benchmarků:
============================= 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/primes5 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collected 4 items test_primes.py .... [100%] -------------------------------------------------------------------------------------------------------- benchmark: 4 tests ------------------------------------------------------------------------------------------------------- Name (time in ns) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- test_primes_0 151.4600 (1.0) 524.9801 (1.0) 154.5897 (1.0) 13.1643 (1.0) 153.5499 (1.0) 0.8599 (1.0) 977;1633 6,468,736.2669 (1.0) 65215 100 test_primes_2 207.6100 (1.37) 797.1500 (1.52) 214.1976 (1.39) 19.9082 (1.51) 212.8500 (1.39) 1.5101 (1.76) 329;2333 4,668,585.6110 (0.72) 47779 100 test_primes_10 2,988.0030 (19.73) 27,753.9948 (52.87) 3,215.3491 (20.80) 723.2469 (54.94) 3,127.0029 (20.36) 54.9917 (63.95) 2480;4210 311,008.2159 (0.05) 96414 1 test_primes_100000 3,287,255.0073 (>1000.0) 3,790,438.0006 (>1000.0) 3,335,102.2510 (>1000.0) 62,347.3199 (>1000.0) 3,309,603.0093 (>1000.0) 37,557.2527 (>1000.0) 27;27 299.8409 (0.00) 291 1 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Legend: Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile. OPS: Operations Per Second, computed as 1 / Mean ============================== 4 passed in 5.62s ===============================
Z výsledků je patrné, že se každý test opakuje několikrát (výchozí hodnotou je jedna sekunda), takže lze spočítat nejkratší a nejrychlejší běh, průměr, odchylku, počet provedených operací aj. Pokud nám nevyhovuje, že je každý test spuštěn pouze tolikrát, dokud není tohoto (či jiného) času dosaženo (poslední dva sloupce), lze specifikovat počet opakování:
$ pytest --benchmark-min-rounds=100000
Taktéž je možné nastavit zavolání testovaného kódu „pro zahřátí“, tj. bez sledování dosaženého času. Tím lze vyloučit klasický problém – pozdní inicializaci, naplnění cache pamětí, možné JIT optimalizace (nikoli v CPythonu) atd.:
$ pytest --benchmark-min-rounds=100000 --benchmark-warmup-iterations=10000
16. Dvě verze algoritmu s jejich porovnáním
V této kapitole si ukážeme dvě možné verze algoritmu pro výpočet prvočísel s využitím Eratosthenova síta. První výpočet je přímočarý (a dalo by se říci, že naivní):
# originální kód lze nalézt na adrese: # http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Using_array_lookup def primes2(limit): is_prime = [False] * 2 + [True] * (limit - 1) for n in range(int(limit**0.5 + 1.5)): # stop at ``sqrt(limit)`` if is_prime[n]: for i in range(n*n, limit+1, n): is_prime[i] = False return [i for i, prime in enumerate(is_prime) if prime]
# originální kód lze nalézt na adrese: # http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Odds-only_version_of_the_array_sieve_above def primes2(limit): """Výpočet seznamu prvočísel až do zadaného limitu.""" # okrajový případ if limit < 2: return [] # druhý případ - 2 je speciálním prvočíslem if limit < 3: return [2] lmtbf = (limit - 3) // 2 # naplnění tabulky, která se bude prosívat buf = [True] * (lmtbf + 1) # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel return [2] + [i + i + 3 for i, v in enumerate(buf) if v]
17. Porovnání výsledků benchmarku
Po úpravě zdrojového kódu je možné benchmark spustit znovu a současně si nechat porovnat nové výsledky s výsledky staršími. Použijeme tedy následující příkaz:
$ pytest --benchmark-compare
Výsledky porovnání vyžadují pohled na test označený příznakem NOW a porovnat ho se stejně pojmenovaným testem, tentokrát (v mém případě) s příznakem 0001_b21f3d5:
test_primes_100000 (NOW) 3,333,477.0051 (>1000.0) 3,857,687.9961 (>1000.0) 3,469,644.2332 (>1000.0) 140,171.5543 (>1000.0) 3,416,847.9979 (>1000.0) 215,252.2557 (>1000.0) 41;0 288.2140 (0.00) 253 1 test_primes_100000 (0001_b21f3d5) 13,335,698.9973 (>1000.0) 17,059,773.0030 (>1000.0) 14,246,669.9671 (>1000.0) 774,731.3323 (>1000.0) 14,071,077.4976 (>1000.0) 776,759.5016 (>1000.0) 9;3 70.1918 (0.00) 60 1
Z těchto výsledků je patrné, že jak průměrný, tak i nejlepší i nejhorší výsledky druhého (optimalizovaného) algoritmu jsou mnohem lepší, než algoritmu řekněme naivního (s přímočarou implementací Eratosthenova síta).
Výsledky porovnání všech testů (benchmarků) vypadají následovně:
============================= 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/primes6 plugins: voluptuous-1.0.2, benchmark-3.2.3, cov-2.5.1 collected 4 items test_primes.py .... [100%] --------------------------------------------------------------------------------------------------------------- benchmark: 8 tests ------------------------------------------------------------------------------------------------------------------ Name (time in ns) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- test_primes_0 (NOW) 150.2500 (1.0) 4,759.9200 (3.09) 156.8916 (1.0) 33.5549 (1.0) 152.5300 (1.0) 0.9000 (1.0) 1203;8878 6,373,826.0269 (1.0) 65181 100 test_primes_2 (NOW) 214.3912 (1.43) 1,542.6087 (1.0) 226.6960 (1.44) 66.0965 (1.97) 219.1740 (1.44) 1.9133 (2.13) 2153;30710 4,411,194.4164 (0.69) 197785 23 test_primes_0 (0001_b21f3d5) 1,925.9969 (12.82) 29,465.9985 (19.10) 2,182.2444 (13.91) 1,118.5913 (33.34) 2,077.9989 (13.62) 58.0039 (64.45) 2728;11359 458,243.8158 (0.07) 177652 1 test_primes_2 (0001_b21f3d5) 2,130.0002 (14.18) 32,203.9996 (20.88) 2,367.3029 (15.09) 1,021.6890 (30.45) 2,283.9959 (14.97) 55.9958 (62.22) 2220;9148 422,421.6525 (0.07) 164420 1 test_primes_10 (NOW) 2,973.9967 (19.79) 30,780.9969 (19.95) 3,230.8981 (20.59) 1,169.5545 (34.85) 3,134.0023 (20.55) 64.0066 (71.12) 628;4001 309,511.4637 (0.05) 68265 1 test_primes_10 (0001_b21f3d5) 3,479.0064 (23.15) 30,729.0029 (19.92) 3,975.2943 (25.34) 1,327.4789 (39.56) 3,756.9989 (24.63) 359.9998 (400.02) 716;2195 251,553.7048 (0.04) 49148 1 test_primes_100000 (NOW) 3,333,477.0051 (>1000.0) 3,857,687.9961 (>1000.0) 3,469,644.2332 (>1000.0) 140,171.5543 (>1000.0) 3,416,847.9979 (>1000.0) 215,252.2557 (>1000.0) 41;0 288.2140 (0.00) 253 1 test_primes_100000 (0001_b21f3d5) 13,335,698.9973 (>1000.0) 17,059,773.0030 (>1000.0) 14,246,669.9671 (>1000.0) 774,731.3323 (>1000.0) 14,071,077.4976 (>1000.0) 776,759.5016 (>1000.0) 9;3 70.1918 (0.00) 60 1 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Legend: Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile. OPS: Operations Per Second, computed as 1 / Mean ============================== 4 passed in 6.25s ===============================
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ě deseti 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:
- 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/ - 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/