Obsah
1. Vyhodnocení kvality testů pomocí mutantů
2. Zjištění pokrytí zdrojového textu jednotkovými či integračními testy
3. Co nám říká a neříká velká míra pokrytí testy?
6. Ukázka nástroje mutmut v praxi
7. Základní použití mutmut krok za krokem
8. Algoritmus pro výpočet prvočísel
9. Jednotkové testy pro funkci pro výpočet prvočísel, výpočet pokrytí kódu testy
10. Využití mutantů pro detekci netestovaných částí kódu
11. Modifikace zdrojových kódů, vylepšení jednotkových testů a obnova původního kódu
12. Opětovné spuštění nástroje mutmut s vylepšenými jednotkovými testy
13. Vylepšení jednotkového testu pro prvních deset prvočísel
14. Třetí spuštění nástroje mutmut
15. Výpočet nejvyššího možného dělitele pro zadaný limit
16. Zbyli ještě nějací mutanti?
18. Repositář s demonstračními příklady
19. Předchozí články s tématem testování (nejenom) v Pythonu
1. Vyhodnocení kvality testů pomocí mutantů
V dnešním článku se seznámíme s nástrojem nazvaným mutmut. Tento nástroj, který je určen pro ekosystém programovacího jazyka Python, je určen k takzvanému testování mutací (mutation testing, i když lepší název je mutation analysis). Úkolem této analýzy je najít takové části zdrojového kódu, které jsou sice zdánlivě dobře pokryty testy (jednotkovými či integračními), ovšem ve skutečnosti se některé části kódu, či jen některé specifické stavy netestují.
Zajímavé je, že testování mutací se nesoustředí přímo na analýzu hotových testů, ale na řízené modifikace původního zdrojového kódu – posléze se zjišťuje, jestli tyto změny vedou k pádu některých testů (což by teoreticky měly). Testování mutací ovšem nedokáže odhalit všechna slabě testovaná místa kódu a ani nedokáže přesně určit, který konkrétní test je nutné rozšířit. Tyto operace je nutné (alespoň prozatím) provádět ručně.
2. Zjištění pokrytí zdrojového textu jednotkovými či integračními testy
Jak jsme si již řekli v perexu dnešního článku, společně s jednotkovými testy (unit tests) a někdy i s integračními testy (integration tests), se mnohdy využívají nástroje, které zjistí míru pokrytí testovaného zdrojového kódu těmito testy. Výsledek může být reprezentován buď spočtením míry pokrytí specifikovanou většinou v procentech (tedy v rozmezí 0% až 100%), nebo je taktéž možné, aby se nějakým způsobem zobrazily testované řádky popř. naopak ty řádky zdrojového kódu, na které testy z nějakého důvodu nedosáhnou.
Tento článek se primárně věnuje nástroji určenému pro ekosystém programovacího jazyka Python. V tomto ekosystému je možné jednotkové nebo integrační testy spouštět například s využitím nástroje pytest doplněného o nástroj pytest-cov. Pokud tyto nástroje použijeme pro spuštění testů, budou výsledky vypadat zhruba následovně:
------------- coverage: platform linux, python 3.11.6-final-0 --------------- Name Stmts Miss Cover Missing ----------------------------------------------------------------------------- app/constants.py 5 5 0% 2-6 app/main.py 21 21 0% 1-50 app/models/config.py 157 79 50% 16-21, 32-44, 54-58, 68-74, 86, 102-104, 107, 117-127, 153-176, 184-188, 191-204 app/utils.py 5 5 0% 1-18 src/cache/__init__.py 0 0 100% src/cache/cache.py 9 2 78% 18, 33 src/cache/cache_factory.py 13 1 92% 21 src/cache/in_memory_cache.py 36 0 100% src/cache/redis_cache.py 29 14 52% 29-33, 45-52, 68, 83-88 src/constants.py 31 0 100% src/docs/__init__.py 0 0 100% src/docs/docs_summarizer.py 42 42 0% 1-112 src/llms/llm_loader.py 112 94 16% 41-53, 56-72, 75-108, 112-157, 161-182, 189-214, 220-271, 274-276 src/query_helpers/__init__.py 0 0 100% src/query_helpers/happy_response_generator.py 26 26 0% 1-61 src/query_helpers/question_validator.py 27 0 100% src/query_helpers/yaml_generator.py 27 27 0% 1-61 src/query_helpers/yes_no_classifier.py 29 0 100% src/ui/__init__.py 0 0 100% src/ui/gradio_ui.py 36 36 0% 1-67 utils/__init__.py 0 0 100% utils/config.py 72 57 21% 29-124 utils/json_tools.py 33 7 79% 9-15, 26 utils/logger.py 33 5 85% 71, 110-118 ----------------------------------------------------------------------------- TOTAL 743 421 43%
Výsledky je možné vyexportovat do různých formátů, například i do HTML stránek:
Obrázek 1: 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.
3. Co nám říká a neříká velká míra pokrytí testy?
Existuje poměrně velké množství projektů, u nichž se dosahuje velmi vysoké míry pokrytí zdrojového kódu jednotkovými testy či testy integračními (i zde má taktéž smysl provádět stejné měření). Ovšem na tomto místě je dobré si uvědomit, že i když se dosáhne stoprocentního pokrytí testy, v žádném případě to neznamená, že bude kód dobře otestován.
Dnes ovšem nemáme na mysli fakt, že jednotkové testy nedokážou (už ze své podstaty) postihnout a detekovat integrační problémy (viz obrázek). Spíše se může stát, že testy sice projdou všemi řádky kódu, ovšem vůbec nejsou testovány mezní stavy.
Obrázek 2: Jednotkové testy nedokáží odhalit problémy na vyšších úrovních abstrakce, například problematické sestavení jednotlivých modulů do vyšších celků.
Uveďme si triviální příklad, a to konkrétně následující funkci:
def is_negative(x: int) -> bool: return x < 0
Stoprocentního pokrytí v tomto případě dosáhneme snadno – zavoláním funkce s libovolným celočíselným parametrem a otestováním výsledku. Ovšem to nám pravděpodobně nic neřekne o tom, jestli je funkce bezchybná. Lepší tedy bude otestovat funkci se záporným parametrem a posléze s parametrem kladným. To je již lepší, ovšem ještě nám chybí možná ta nejdůležitější část: otestovat chování funkce okolo mezní hodnoty, tedy okolo nuly. Tím se otestují i problematické chyby typu „off-by-one“:
def is_negative(x: int) -> bool: return x <= 0
V tomto konkrétním případě je to snadné, ovšem v reálných programech jsou použity mnohdy složité podmínky atd., takže bývá komplikované napsat všechny potřebné jednotkové testy (či vůbec zjistit, které stavy se musí otestovat).
4. Mutanti se představují
Některé problémy, které nejsou dobře otestovány, lze detekovat s využitím takzvaných mutantů. Jedná se o techniku založenou na modifikaci zdrojových kódů, které jsou testovány. Ve zdrojových kódech je vždy modifikována jedna malá a dobře izolovaná část (například podmínka) a následně jsou spuštěny jednotkové či integrační testy. Předpokládá se, že by testy měly zhavarovat – ostatně zdrojové kódy se změnily a testy nikoli, takže by tato změna měla vést k pádu alespoň jednoho testu. Pokud tomu tak není, tedy v případě, že jsou testy dokončeny bez detekce chyby, je změna provedená ve zdrojovém kódu zaznamenána a nástroj pokračuje s další změnou. Pokud nějaký test naopak havaruje, je vše v pořádku – změna byla správně detekována.
Samotné změny prováděné ve zdrojových kódech bývají velmi malé. Jedná se například o:
- Změnu podmínky z < na <= a naopak (i další varianty)
- Otočení (negace) podmínky
- Změnu celočíselných konstant o jedničku
- Přidání znaků na začátek a konec řetězcových literálů
- Náhradu nějakého přiřazení typu x=foo() za x=Null
5. Nástroj mutmut
Výše uvedený postup je nabízen hned několika nástroji určenými pro ekosystém programovacího jazyka Python. Jeden z těchto nástrojů se jmenuje mutmut. V dalším textu si ukážeme základní způsoby jeho použití. Nejdříve si pochopitelně musíme tento nástroj nainstalovat, což je snadné, protože se jedná o balíček, který nemá žádné přímé závislosti (interně však bude volat pytest). Instalace nástroje mutmut může být provedena pro aktuálně přihlášeného uživatele takto:
$ pip install --user mutmut
Popř. na systémech, které rozlišují Python 2.x a Python 3.x:
$ pip3 install --user mutmut
Po instalaci si rychle zkontrolujeme, zda je nástroj mutmut nainstalován a zda je na $PATH:
$ mutmut Usage: mutmut [OPTIONS] COMMAND [ARGS]... Mutation testing system for Python. Options: -h, --help Show this message and exit. Commands: apply Apply a mutation on disk. html Generate a HTML report of surviving mutants. junitxml Show a mutation diff with junitxml format. result-ids Print the IDs of the specified mutant classes (separated by... results Print the results. run Runs mutmut. show Show a mutation diff. version Show the version and exit.
6. Ukázka nástroje mutmut v praxi
Podívejme se nyní, jak může vypadat použití nástroje mutmut v praxi. V adresáři s projektem, v němž se běžné testy spouští příkazem pytest či python -m pytest, spustíme namísto toho následující příkaz:
$ mutmut run
Nástroj mutmut nejdříve projde zdrojovými kódy a zjistí všechna místa, která bude postupně mutovat. Těchto míst může být – samozřejmě v závislosti na velikosti projektu – jen několik ale i několik desítek tisíc. Výchozí omezení je na 500 mutací. Následně se do zdrojových kódů přidávají mutace (tedy mění se vybrané konstanty, podmínky a přiřazení) a pro každou mutaci jsou spouštěny jednotkové či integrační testy. Každý běh těchto testů může skončit pěti způsoby, které jsou vypsány přímo nástrojem mutmut:
- Mutation testing starting - These are the steps: 1. A full test suite run will be made to make sure we can run the tests successfully and we know how long it takes (to detect infinite loops for example) 2. Mutants will be generated and checked Results are stored in .mutmut-cache. Print found mutants with `mutmut results`. Legend for output: 🎉 Killed mutants. The goal is for everything to end up in this bucket. ⏰ Timeout. Test suite took 10 times as long as the baseline so were killed. 🤔 Suspicious. Tests took a long time, but not long enough to be fatal. 🙁 Survived. This means your tests need to be expanded. 🔇 Skipped. Skipped. 1. Using cached time for baseline tests, to run baseline again delete the cache file 2. Checking mutants ⠼ 105/500 🎉 23 ⏰ 0 🤔 0 🙁 82 🔇 0
Z průběžných výsledků (105 mutací z celkových 500) je patrné, že bylo nalezeno již 82 mutací zdrojového kódu, které nevedlo k detekci chyby v testech. Buď se jedná o kód nepokrytý testy nebo o kód, který je sice pokrytý, ale zdaleka ne vše se v testech kontroluje (mezní hodnoty, chyby typu ±1, hodnoty řetězců atd.).
Analýza může trvat velmi dlouho, ovšem dobré je, že nástroj mutmut lze kdykoli ukončit a neztratit tak průběžné výsledky této analýzy. Tyto výsledky jsou totiž ukládány do lokální cache na disku. To znamená, že po jakémkoli ukončení nástroje mutmut (buď jeho ukončením z klávesnice, nebo až proběhnou všechny analýzy) je možné si výsledky prohlédnout.
Nejprve spustíme následující příkaz, který vypíše základní informace o tom, u kterých částí kódu byly zjištěny nedostatky:
$ mutmut results
Vypsané informace:
To apply a mutant on disk: mutmut apply <id> To show a mutant: mutmut show <id> Survived 🙁 (452) ---- src/cache/cache.py (2) ---- 54-55 ---- src/cache/cache_factory.py (2) ---- 56, 59 ---- src/cache/in_memory_cache.py (2) ---- 74, 78 ... ... ...
Samozřejmě si můžeme prohlédnout i jednotlivé mutace, přesněji řečeno takové mutace, které nebyly zachyceny testy.
Mutace číslo 55 například odstranila dekorátor @abstractmethod:
$ mutmut show 55 --- src/cache/cache.py +++ src/cache/cache.py @@ -15,7 +15,6 @@ """ pass - @abstractmethod def insert_or_append(self, key: str, value: str) -> None: """Abstract method to store a value in the cache.
Další mutace změnila řetězcový literál, což testy opět nezachytily, i když je daná část kódu zdánlivě testy pokryta (ve skutečnosti se používal mock, ale to na věci nic nemění):
--- src/cache/redis_cache.py +++ src/cache/redis_cache.py @@ -41,7 +41,7 @@ None """ self.redis_client = redis.StrictRedis( - host=os.environ.get("REDIS_CACHE_HOST", constants.REDIS_CACHE_HOST), + host=os.environ.get("XXREDIS_CACHE_HOSTXX", constants.REDIS_CACHE_HOST), port=os.environ.get("REDIS_CACHE_PORT", constants.REDIS_CACHE_PORT), decode_responses=True, )
7. Základní použití mutmut krok za krokem
Ve druhé části článku si ukážeme, jakým způsobem je možné nástroj mutmut použít pro postupné nalézání nedostatků v jednotkových testech napsaných pro velmi krátký algoritmus – bude se konkrétně jednat o funkci s necelými patnácti řádky zdrojového kódu. Uvidíme, že už první verze jednotkových testů sice kód pokryje ze 100%, což ovšem neznamená, že byly otestovány všechny potenciální problémy.
8. Algoritmus pro výpočet prvočísel
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 mutmut. Skript, který budeme testovat, se jmenuje primes.py a obsahuje jednu možnou 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 find_primes(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]
Prvních 1000 prvočísel vypočítaných tímto algoritmem by mělo vypadat následovně:
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997
9. Jednotkové testy pro funkci pro výpočet prvočísel, výpočet pokrytí kódu testy
Pro otestování algoritmu realizovaného funkcí find_primes nejprve použijeme tři jednotkové testy. První test částečně ověří, jaké hodnoty jsou vráceny (či naopak chybí) v seznamu prvočísel v rozsahu od 2 do 10. Druhý test ověří, že prvočísla začínají až od hodnoty 2 a třetí test zjistí, že dvojka je prvočíslem:
"""Implementace jednotkových testů.""" from primes import find_primes 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 = find_primes(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 = find_primes(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 = find_primes(2) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert 2 in p assert p == [2]
Jednotkové testy nyní spustíme a necháme si přitom vypsat tabulku s výpočtem pokrytí kódu testy i s případnými řádky, které nejsou otestovány:
$ pytest --cov=primes --cov-report term-missing
Výsledkem by měla být zhruba tato tabulka. Samozřejmě se může lišit verze Pythonu, verze balíčku pytest i seznam nainstalovaných pluginů, ovšem vlastní informace o tom, že všech 13 programových řádků bylo otestováno:
============================= test session starts ============================== platform linux -- Python 3.11.6, pytest-7.4.4, pluggy-1.3.0 rootdir: /home/ptisnovs/xy/primes1 plugins: anyio-3.7.1, cov-4.1.0, monkeytype-1.1.0 collected 3 items test_primes.py ... [100%] ---------- coverage: platform linux, python 3.11.6-final-0 ----------- Name Stmts Miss Cover Missing ----------------------------------------- primes.py 13 0 100% ----------------------------------------- TOTAL 13 0 100% ============================== 3 passed in 0.01s ===============================
10. Využití mutantů pro detekci netestovaných částí kódu
Z předchozí tabulky by se mohlo zdát, že jsme celou realizaci algoritmu otestovali velmi dobře; ostatně zdaleka ne všechen kód, který kdy vytvoříme, bude mít 100% pokrytí testy. Zkusme si tedy toto tvrzení ověřit nástrojem mutmut. Namísto příkazu pytest použijeme příkaz mutmut, kterému ovšem v takto jednoduchém projektu (nemá samostatný adresář pro testy) musíme předat jméno zdrojového kódu, který budeme chtít „mutovat“ a hledat tak mezery v testech:
$ mutmut run --paths-to-mutate primes.py
Průběh testování s mutanty by měl vypadat následovně:
- Mutation testing starting - These are the steps: 1. A full test suite run will be made to make sure we can run the tests successfully and we know how long it takes (to detect infinite loops for example) 2. Mutants will be generated and checked Results are stored in .mutmut-cache. Print found mutants with `mutmut results`. Legend for output: 🎉 Killed mutants. The goal is for everything to end up in this bucket. ⏰ Timeout. Test suite took 10 times as long as the baseline so were killed. 🤔 Suspicious. Tests took a long time, but not long enough to be fatal. 🙁 Survived. This means your tests need to be expanded. 🔇 Skipped. Skipped. mutmut cache is out of date, clearing it... 1. Running tests without mutations ⠏ Running...Done 2. Checking mutants ⠙ 44/44 🎉 23 ⏰ 0 🤔 0 🙁 21 🔇 0
Povšimněte si, že původní zdrojový kód byl mutován 44× a přitom se objevilo 21 mutací, které nebyly zachyceny jednotkovými testy. To se může zdát jako velmi vysoké číslo, ale uvidíme, že některé mutace jsou si velmi podobné. Můžeme si je nechat vypsat:
$ mutmut show all
Výsledky, které získáme:
To apply a mutant on disk: mutmut apply <id> To show a mutant: mutmut show <id> Survived 🙁 (21) ---- primes.py (21) ---- # mutant 3 --- primes.py +++ primes.py @@ -11,7 +11,7 @@ return [] # druhý případ - 2 je speciálním prvočíslem - if limit < 3: + if limit <= 3: return [2] lmtbf = (limit - 3) // 2 # mutant 4 --- primes.py +++ primes.py @@ -11,7 +11,7 @@ return [] # druhý případ - 2 je speciálním prvočíslem - if limit < 3: + if limit < 4: return [2] lmtbf = (limit - 3) // 2 # mutant 6 --- primes.py +++ primes.py @@ -14,7 +14,7 @@ if limit < 3: return [2] - lmtbf = (limit - 3) // 2 + lmtbf = (limit + 3) // 2 # naplnění tabulky, která se bude prosívat buf = [True] * (lmtbf + 1) # mutant 7 --- primes.py +++ primes.py @@ -14,7 +14,7 @@ if limit < 3: return [2] - lmtbf = (limit - 3) // 2 + lmtbf = (limit - 4) // 2 # naplnění tabulky, která se bude prosívat buf = [True] * (lmtbf + 1) # mutant 9 --- primes.py +++ primes.py @@ -14,7 +14,7 @@ if limit < 3: return [2] - lmtbf = (limit - 3) // 2 + lmtbf = (limit - 3) // 3 # naplnění tabulky, která se bude prosívat buf = [True] * (lmtbf + 1) # mutant 11 --- primes.py +++ primes.py @@ -17,7 +17,7 @@ lmtbf = (limit - 3) // 2 # naplnění tabulky, která se bude prosívat - buf = [True] * (lmtbf + 1) + buf = [False] * (lmtbf + 1) # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): # mutant 14 --- primes.py +++ primes.py @@ -17,7 +17,7 @@ lmtbf = (limit - 3) // 2 # naplnění tabulky, která se bude prosívat - buf = [True] * (lmtbf + 1) + buf = [True] * (lmtbf + 2) # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): # mutant 16 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit * 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 18 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) + 3) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 19 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 4) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 21 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 3 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 22 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 2 - 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 23 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 2 + 2): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 24 --- primes.py +++ primes.py @@ -22,7 +22,7 @@ # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: - p = i + i + 3 + p = i - i + 3 s = p * (i + 1) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # mutant 26 --- primes.py +++ primes.py @@ -22,7 +22,7 @@ # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: - p = i + i + 3 + p = i + i + 4 s = p * (i + 1) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # mutant 30 --- primes.py +++ primes.py @@ -23,7 +23,7 @@ for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 - s = p * (i + 1) + i + s = p * (i + 2) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel # mutant 31 --- primes.py +++ primes.py @@ -23,7 +23,7 @@ for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 - s = p * (i + 1) + i + s = p * (i + 1) - i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel # mutant 33 --- primes.py +++ primes.py @@ -24,7 +24,7 @@ if buf[i]: p = i + i + 3 s = p * (i + 1) + i - buf[s::p] = [False] * ((lmtbf - s) // p + 1) + buf[s::p] = [True] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel return [2] + [i + i + 3 for i, v in enumerate(buf) if v] # mutant 42 --- primes.py +++ primes.py @@ -27,5 +27,5 @@ 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] + return [2] + [i - i + 3 for i, v in enumerate(buf) if v] # mutant 43 --- primes.py +++ primes.py @@ -27,5 +27,5 @@ 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] + return [2] + [i + i - 3 for i, v in enumerate(buf) if v] # mutant 44 --- primes.py +++ primes.py @@ -27,5 +27,5 @@ 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] + return [2] + [i + i + 4 for i, v in enumerate(buf) if v]
11. Modifikace zdrojových kódů, vylepšení jednotkových testů a obnova původního kódu
Nyní si můžeme příkazem:
$ mutmut apply (ID mutace)
nechat nástrojem mutmut modifikovat zdrojové kódy příslušnou mutací. V takovém případě by testy neměly zhavarovat (což už nástroj mutmut ověřil při své analýze) a úkolem programátora je naopak zajistit, aby tato mutace vedla k detekci chyby. Po vylepšení jednotkových testů se vrátíme k původní verzi zdrojových kódů (pomoci mohou systémy pro správu verzí atd.).
Zajímavé je, že celkem devět nalezených mutací lze zachytit jediným novým testem, konkrétně testem, zda se v intervalu 2..3 nachází dvě prvočísla s hodnotami (logicky) 2 a 3. Tento test tedy přidáme do naší sady jednotkových testů. Na tomto místě už bude vhodné zvážit, zda namísto kopírování kódu testů nepoužít jedinou funkci, které se bude předávat vstupní parametr do testovaného algoritmu a očekávaný výsledek. Toto řešení jsme si už na Rootu ukazovali, a to konkrétně zde, takže se jím můžete nechat inspirovat. Nová sada jednotkových testů vypadá následovně:
"""Implementace jednotkových testů.""" from primes import find_primes 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 = find_primes(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 = find_primes(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 = find_primes(2) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert 2 in p assert p == [2] def test_primes_3(): """Otestování výpočtu seznamu prvočísel do limitu 3.""" p = find_primes(3) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert p == [2, 3]
12. Opětovné spuštění nástroje mutmut s vylepšenými jednotkovými testy
Znovu se pokusíme o analýzu našeho jednoduchého projektu. Tentokrát by nástroj mutmut měl najít menší množství mutantů, což je pochopitelně dobré si ověřit:
- Mutation testing starting - These are the steps: 1. A full test suite run will be made to make sure we can run the tests successfully and we know how long it takes (to detect infinite loops for example) 2. Mutants will be generated and checked Results are stored in .mutmut-cache. Print found mutants with `mutmut results`. Legend for output: 🎉 Killed mutants. The goal is for everything to end up in this bucket. ⏰ Timeout. Test suite took 10 times as long as the baseline so were killed. 🤔 Suspicious. Tests took a long time, but not long enough to be fatal. 🙁 Survived. This means your tests need to be expanded. 🔇 Skipped. Skipped. mutmut cache is out of date, clearing it... 1. Running tests without mutations ⠏ Running...Done 2. Checking mutants ⠏ 44/44 🎉 32 ⏰ 0 🤔 0 🙁 12 🔇 0
Ze zobrazených výsledků je patrné, že tomu tak skutečně je – namísto 21 mutantů se nyní nalezlo jen 12 mutantů. Takže si zbývající mutanty vypišme:
To apply a mutant on disk: mutmut apply <id> To show a mutant: mutmut show <id> Survived 🙁 (12) ---- primes.py (12) ---- # mutant 9 --- primes.py +++ primes.py @@ -14,7 +14,7 @@ if limit < 3: return [2] - lmtbf = (limit - 3) // 2 + lmtbf = (limit - 3) // 3 # naplnění tabulky, která se bude prosívat buf = [True] * (lmtbf + 1) # mutant 16 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit * 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 19 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 4) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 21 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 3 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 22 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 2 - 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 23 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 2 + 2): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 24 --- primes.py +++ primes.py @@ -22,7 +22,7 @@ # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: - p = i + i + 3 + p = i - i + 3 s = p * (i + 1) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # mutant 26 --- primes.py +++ primes.py @@ -22,7 +22,7 @@ # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: - p = i + i + 3 + p = i + i + 4 s = p * (i + 1) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # mutant 30 --- primes.py +++ primes.py @@ -23,7 +23,7 @@ for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 - s = p * (i + 1) + i + s = p * (i + 2) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel # mutant 31 --- primes.py +++ primes.py @@ -23,7 +23,7 @@ for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 - s = p * (i + 1) + i + s = p * (i + 1) - i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel # mutant 33 --- primes.py +++ primes.py @@ -24,7 +24,7 @@ if buf[i]: p = i + i + 3 s = p * (i + 1) + i - buf[s::p] = [False] * ((lmtbf - s) // p + 1) + buf[s::p] = [True] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel return [2] + [i + i + 3 for i, v in enumerate(buf) if v] # mutant 42 --- primes.py +++ primes.py @@ -27,5 +27,5 @@ 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] + return [2] + [i - i + 3 for i, v in enumerate(buf) if v]
Nyní jsou mutanti nalezeni pouze na několika řádcích původního zdrojového kódu, což je dobře, protože nám to umožní se soustředit na to, jak testy ještě více vylepšit.
13. Vylepšení jednotkového testu pro prvních deset prvočísel
Při revizi jednotkových testů narazíme na tento zvláštní (ovšem v mnoha projektech typický) pattern – sice se spouští funkce vracející pro pevně daný a známý vstup nějaké hodnoty, ovšem návratová hodnota funkce je otestována pouze poněkud vágně. Příkladem může být jednotkový test, který má sice ve svém popisu uvedeno, že testuje výpočet prvočísel až do limitu 10, ale ve skutečnosti otestuje pouze dvě podmínky ze seznamu, který je vrácen:
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 = find_primes(10) # testy lze dále rozšiřovat assert 2 in p assert 10 not in p
Zkusme si tento test rozšířit tak, že bude očekávat jediný korektní výsledek a jakékoli odchylky ihned povedou k detekci chyby:
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 = find_primes(10) # testy lze dále rozšiřovat assert p == [2, 3, 5, 7]
Výsledná sada jednotkových testů bude vypadat následovně:
"""Implementace jednotkových testů.""" from primes import find_primes 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 = find_primes(10) # testy lze dále rozšiřovat assert p == [2, 3, 5, 7] def test_primes_0(): """Otestování výpočtu seznamu prvočísel do limitu 0.""" p = find_primes(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 = find_primes(2) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert 2 in p assert p == [2] def test_primes_3(): """Otestování výpočtu seznamu prvočísel do limitu 3.""" p = find_primes(3) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert p == [2, 3]
14. Třetí spuštění nástroje mutmut
Rozšíření přesnosti jednotkového testu by mělo vést k opětovnému zmenšení počtu mutantů. To si ověříme snadno opětovným spuštěním nástroje mutmut:
To apply a mutant on disk: mutmut apply <id> To show a mutant: mutmut show <id> Survived 🙁 (6)
Nyní bylo nalezeno už pouze šest mutantů, takže se na ně podívejme:
---- primes.py (6) ---- # mutant 9 --- primes.py +++ primes.py @@ -14,7 +14,7 @@ if limit < 3: return [2] - lmtbf = (limit - 3) // 2 + lmtbf = (limit - 3) // 3 # naplnění tabulky, která se bude prosívat buf = [True] * (lmtbf + 1) # mutant 16 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit * 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 21 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 3 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 23 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 2 + 2): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 24 --- primes.py +++ primes.py @@ -22,7 +22,7 @@ # vlastní prosívání for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: - p = i + i + 3 + p = i - i + 3 s = p * (i + 1) + i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # mutant 31 --- primes.py +++ primes.py @@ -23,7 +23,7 @@ for i in range((int(limit ** 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 - s = p * (i + 1) + i + s = p * (i + 1) - i buf[s::p] = [False] * ((lmtbf - s) // p + 1) # vytvoření seznamu prvočísel
15. Výpočet nejvyššího možného dělitele pro zadaný limit
Většina mutantů, které byly nalezeny v rámci předchozí analýzy, se týká té části kódu, která počítá nejvyšší možné prvočíslo pro zadaný limit. Tedy například pro limit 10 bude nejvyšší možný dělitel neprvočísla odvozen od druhé odmocniny limitu (větší dělitele netřeba testovat, protože nám stačí najít menší dělitel, pokud pochopitelně existuje). Prozatím jsme testovali velmi malé rozsahy hodnot, ve kterých jsme hledali prvočísla. Zkusme si tedy – čistě pro trénink – tento rozsah rozšířit na prvočísla v rozsahu od 2 do 1000 (což už je ale spíše nepraktické pro jiné algoritmy):
"""Implementace jednotkových testů.""" from primes import find_primes 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 = find_primes(10) # testy lze dále rozšiřovat assert p == [2, 3, 5, 7] def test_primes_0(): """Otestování výpočtu seznamu prvočísel do limitu 0.""" p = find_primes(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 = find_primes(2) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert 2 in p assert p == [2] def test_primes_3(): """Otestování výpočtu seznamu prvočísel do limitu 3.""" p = find_primes(3) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert p == [2, 3] def test_primes_100(): """Otestování výpočtu seznamu prvočísel do limitu 1000.""" p = find_primes(1000) # otestujeme, zda sekvence obsahuje pouze hodnotu 2 assert p == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]
16. Zbyli ještě nějací mutanti?
Po provedení poslední analýzy kódu nám zůstane pouze dvojice mutantů. V tomto případě se ovšem nejedná ani o chybu v kódu, ani o chybějící jednotkový test, protože tito mutanti pouze rozšiřují oblast, v níž se hledají prvočísla. Jde tedy spíše o větší neefektivitu algoritmu, což je již oblast mimo dosah klasických jednotkových testů.
To apply a mutant on disk: mutmut apply <id> To show a mutant: mutmut show <id> Survived 🙁 (2) ---- primes.py (2) ---- # mutant 16 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit * 0.5) - 3) // 2 + 1): if buf[i]: p = i + i + 3 s = p * (i + 1) + i # mutant 23 --- primes.py +++ primes.py @@ -20,7 +20,7 @@ buf = [True] * (lmtbf + 1) # vlastní prosívání - for i in range((int(limit ** 0.5) - 3) // 2 + 1): + for i in range((int(limit ** 0.5) - 3) // 2 + 2): if buf[i]: p = i + i + 3 s = p * (i + 1) + i
17. Shrnutí
Nástroj mutmut je jako doplněk k dalším nástrojům určeným pro analýzu projektů psaných v Pythonu poměrně účinný a dokáže odhalit i různé nástrahy ve vlastních programech, nejenom nedostatky jednotkových testů. Jeho nevýhodou je však dlouhá doba běhu, která může dosahovat desítek minut a u větších projektů pravděpodobně i hodin (tj. jedná se spíše o pomůcku vývojáře a nikoli o nástroj pro CI). V takovém případě je možná vhodné se zamyslet nad vlastní modulárností celého řešení.
18. Repositář s demonstračními příklady
Všechny demonstrační příklady určené pro otestování vlastností nástroje mutmut naleznete v repositáři https://github.com/tisnik/most-popular-python-libs:
# | Příklad | Stručný popis | Adresa |
---|---|---|---|
1 | primes1 | výpočet prvočísel, první sada jednotkových testů | https://github.com/tisnik/most-popular-python-libs/blob/master/mutmut/primes1 |
2 | primes2 | výpočet prvočísel, druhá sada jednotkových testů | https://github.com/tisnik/most-popular-python-libs/blob/master/mutmut/primes2 |
3 | primes3 | výpočet prvočísel, třetí sada jednotkových testů | https://github.com/tisnik/most-popular-python-libs/blob/master/mutmut/primes3 |
4 | primes4 | výpočet prvočísel, čtvrtá sada jednotkových testů | https://github.com/tisnik/most-popular-python-libs/blob/master/mutmut/primes4 |
19. Předchozí články s tématem testování (nejenom) v Pythonu
Tématem testování jsme se již na stránkách Roota několikrát zabývali. Většinou jsme se zaměřili na popis nástrojů a postupů pro ekosystémy programovacích jazyků Python a Go. 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/ - Nástroj pytest a jednotkové testy: fixtures, výjimky, parametrizace testů
https://www.root.cz/clanky/nastroj-pytest-a-jednotkove-testy-fixtures-vyjimky-parametrizace-testu/ - Nástroj pytest a jednotkové testy: životní cyklus testů, užitečné tipy a triky
https://www.root.cz/clanky/nastroj-pytest-a-jednotkove-testy-zivotni-cyklus-testu-uzitecne-tipy-a-triky/ - Struktura projektů s jednotkovými testy, využití Travis CI
https://www.root.cz/clanky/struktura-projektu-s-jednotkovymi-testy-vyuziti-travis-ci/ - Omezení stavového prostoru testovaných funkcí a metod
https://www.root.cz/clanky/omezeni-stavoveho-prostoru-testovanych-funkci-a-metod/ - Testování aplikací s využitím nástroje Hypothesis
https://www.root.cz/clanky/testovani-aplikaci-s-vyuzitim-nastroje-hypothesis/ - Testování aplikací s využitím nástroje Hypothesis (dokončení)
https://www.root.cz/clanky/testovani-aplikaci-s-vyuzitim-nastroje-hypothesis-dokonceni/ - Testování webových aplikací s REST API z Pythonu
https://www.root.cz/clanky/testovani-webovych-aplikaci-s-rest-api-z-pythonu/ - Testování webových aplikací s REST API z Pythonu (2)
https://www.root.cz/clanky/testovani-webovych-aplikaci-s-rest-api-z-pythonu-2/ - 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
- Mutmut: a Python mutation testing system
https://hackernoon.com/mutmut-a-python-mutation-testing-system-9b9639356c78 - mutmut – python mutation tester
https://github.com/boxed/mutmut - Mutation testing
https://en.wikipedia.org/wiki/Mutation_testing - mutmut 2.4.4
https://pypi.org/project/mutmut/ - Prime formulas and polynomial functions
https://en.wikipedia.org/wiki/Formula_for_primes#Prime_formulas_and_polynomial_functions - Prime-Generating Polynomial
https://mathworld.wolfram.com/Prime-GeneratingPolynomial.html - Hoare logic
https://en.wikipedia.org/wiki/Hoare_logic - Goto Fail, Heartbleed, and Unit Testing Culture
https://martinfowler.com/articles/testing-culture.html - PEP-484
https://www.python.org/dev/peps/pep-0484/ - In-depth: Functional programming in C++
https://www.gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php - mypy
http://www.mypy-lang.org/ - Welcome to Mypy documentation!
https://mypy.readthedocs.io/en/latest/index.html - mypy na GitHubu
https://github.com/python/mypy - mypy 0.770 na PyPi
https://pypi.org/project/mypy/ - Extensions for mypy (separated out from mypy/extensions)
https://github.com/python/mypy_extensions - The Mypy Blog
https://mypy-lang.blogspot.com/2020/03/mypy-0770-released.html - Our journey to type checking 4 million lines of Python
https://dropbox.tech/application/our-journey-to-type-checking-4-million-lines-of-python - Type-Checking Python Programs With Type Hints and mypy
https://www.youtube.com/watch?v=2×WhaALHTvU - Refactoring to Immutability – Kevlin Henney
https://www.youtube.com/watch?v=APUCMSPiNh4 - Bernat Gabor – Type hinting (and mypy) – PyCon 2019
https://www.youtube.com/watch?v=hTrjTAPnA_k - Stanford Seminar – Optional Static Typing for Python
https://www.youtube.com/watch?v=GiZKuyLKvAA - mypy Getting to Four Million Lines of Typed Python – Michael Sullivan
https://www.youtube.com/watch?v=FT_WHV4-QcU - Shebang
https://en.wikipedia.org/wiki/Shebang_(Unix) - pytest 5.4.2 na PyPi
https://pypi.org/project/pytest/ - Hillel Wayne – Beyond Unit Tests: Taking Your Testing to the Next Level – PyCon 2018
https://www.youtube.com/watch?v=MYucYon2-lk - 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/ - What is Property Based Testing?
https://hypothesis.works/articles/what-is-property-based-testing/ - 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 - pytest-print
https://pytest-print.readthedocs.io/en/latest/ - Continuous integration
https://en.wikipedia.org/wiki/Continuous_integration - Travis CI
https://travis-ci.org/ - Mutation testing
https://en.wikipedia.org/wiki/Mutation_testing - Články o Hypothesis
https://news.ycombinator.com/from?site=hypothesis.works - Testovací případ
https://cs.wikipedia.org/wiki/Testovac%C3%AD_p%C5%99%C3%ADpad - Most testing is ineffective
https://hypothesis.works/