Vyhodnocení kvality testů pomocí mutantů

16. 1. 2024
Doba čtení: 37 minut

Sdílet

Ilustrační snímek Autor: Depositphotos
Ilustrační snímek
U jednotkových testů a někdy i u testů integračních se zjišťuje míra pokrytí zdrojového kódu testy. Výsledkem je určité procento pokrytí. Ovšem ukazuje se, že i kód s velkou mírou pokrytí ve skutečnosti nemusí být příliš dobře otestován.

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?

4. Mutanti se představují

5. Nástroj mutmut

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?

17. Shrnutí

18. Repositář s demonstračními příklady

19. Předchozí články s tématem testování (nejenom) v Pythonu

20. Odkazy na Internetu

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%
Poznámka: výsledek 43% pokrytí kódu jednotkovými testy je v tomto případě poměrně slabý.

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.

Poznámka: to znamená, že testování mutantů je dosti pomalé, protože každá změna provedená ve zdrojových kódech vyžaduje opětovné spuštění všech testů.

Samotné změny prováděné ve zdrojových kódech bývají velmi malé. Jedná se například o:

  1. Změnu podmínky z < na <= a naopak (i další varianty)
  2. Otočení (negace) podmínky
  3. Změnu celočíselných konstant o jedničku
  4. Přidání znaků na začátek a konec řetězcových literálů
  5. Náhradu nějakého přiřazení typu x=foo() za x=Null
Poznámka: další práce je již manuální. Jedná se o aplikaci změn na zdrojové kódy (to nástroje ještě provedou automaticky) a modifikaci či přidání testů tak, aby došlo k detekci právě zavedené chyby. Posléze se modifikace kódu vrátí zpět (takže původní zdrojový kód aplikace zůstane nepozměněn) a následně se zkontroluje, zda testy prochází bez chyby.

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.).

Poznámka: tato hodnota je sice poměrně vysoká, ale při pozdějším zkoumání mutací zjistíme, že mnohé z nich se vlastně týkají stejné části zdrojového kódu. Což znamená, že úprav jednotkových testů bude v praxi méně, než by tato hodnota mohla naznačovat.

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í.

bitcoin školení listopad 24

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:

  1. 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/
  2. 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/
  3. 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/
  4. 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/
  5. 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/
  6. Struktura projektů s jednotkovými testy, využití Travis CI
    https://www.root.cz/clanky/struktura-projektu-s-jednotkovymi-testy-vyuziti-travis-ci/
  7. Omezení stavového prostoru testovaných funkcí a metod
    https://www.root.cz/clanky/omezeni-stavoveho-prostoru-testovanych-funkci-a-metod/
  8. Testování aplikací s využitím nástroje Hypothesis
    https://www.root.cz/clanky/testovani-aplikaci-s-vyuzitim-nastroje-hypothesis/
  9. Testování aplikací s využitím nástroje Hypothesis (dokončení)
    https://www.root.cz/clanky/testovani-aplikaci-s-vyuzitim-nastroje-hypothesis-dokonceni/
  10. Testování webových aplikací s REST API z Pythonu
    https://www.root.cz/clanky/testovani-webovych-aplikaci-s-rest-api-z-pythonu/
  11. 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/
  12. 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/
  13. 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/
  14. 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/
  15. 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/
  16. Validace datových struktur v Pythonu (2. část)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-2-cast/
  17. Validace datových struktur v Pythonu (dokončení)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-dokonceni/
  18. Univerzální testovací nástroj Robot Framework
    https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework/
  19. Univerzální testovací nástroj Robot Framework a BDD testy
    https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework-a-bdd-testy/
  20. Úvod do problematiky fuzzingu a fuzz testování
    https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani/
  21. Ú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/
  22. 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/
  23. 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/
  24. Testování aplikací naprogramovaných v jazyce Go
    https://www.root.cz/clanky/testovani-aplikaci-naprogramovanych-v-jazyce-go/
  25. 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/
  26. 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/
  27. 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/
  28. 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/
  29. 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/
  30. 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/
  31. 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/
  32. 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

  1. Mutmut: a Python mutation testing system
    https://hackernoon.com/mutmut-a-python-mutation-testing-system-9b9639356c78
  2. mutmut – python mutation tester
    https://github.com/boxed/mutmut
  3. Mutation testing
    https://en.wikipedia.org/wi­ki/Mutation_testing
  4. mutmut 2.4.4
    https://pypi.org/project/mutmut/
  5. Prime formulas and polynomial functions
    https://en.wikipedia.org/wi­ki/Formula_for_primes#Pri­me_formulas_and_polynomial_fun­ctions
  6. Prime-Generating Polynomial
    https://mathworld.wolfram.com/Prime-GeneratingPolynomial.html
  7. Hoare logic
    https://en.wikipedia.org/wi­ki/Hoare_logic
  8. Goto Fail, Heartbleed, and Unit Testing Culture
    https://martinfowler.com/ar­ticles/testing-culture.html
  9. PEP-484
    https://www.python.org/dev/peps/pep-0484/
  10. In-depth: Functional programming in C++
    https://www.gamasutra.com/vi­ew/news/169296/Indepth_Fun­ctional_programming_in_C.php
  11. mypy
    http://www.mypy-lang.org/
  12. Welcome to Mypy documentation!
    https://mypy.readthedocs.i­o/en/latest/index.html
  13. mypy na GitHubu
    https://github.com/python/mypy
  14. mypy 0.770 na PyPi
    https://pypi.org/project/mypy/
  15. Extensions for mypy (separated out from mypy/extensions)
    https://github.com/python/my­py_extensions
  16. The Mypy Blog
    https://mypy-lang.blogspot.com/2020/03/mypy-0770-released.html
  17. Our journey to type checking 4 million lines of Python
    https://dropbox.tech/application/our-journey-to-type-checking-4-million-lines-of-python
  18. Type-Checking Python Programs With Type Hints and mypy
    https://www.youtube.com/wat­ch?v=2×WhaALHTvU
  19. Refactoring to Immutability – Kevlin Henney
    https://www.youtube.com/wat­ch?v=APUCMSPiNh4
  20. Bernat Gabor – Type hinting (and mypy) – PyCon 2019
    https://www.youtube.com/wat­ch?v=hTrjTAPnA_k
  21. Stanford Seminar – Optional Static Typing for Python
    https://www.youtube.com/wat­ch?v=GiZKuyLKvAA
  22. mypy Getting to Four Million Lines of Typed Python – Michael Sullivan
    https://www.youtube.com/wat­ch?v=FT_WHV4-QcU
  23. Shebang
    https://en.wikipedia.org/wi­ki/Shebang_(Unix)
  24. pytest 5.4.2 na PyPi
    https://pypi.org/project/pytest/
  25. Hillel Wayne – Beyond Unit Tests: Taking Your Testing to the Next Level – PyCon 2018
    https://www.youtube.com/wat­ch?v=MYucYon2-lk
  26. Awesome Python – testing
    https://github.com/vinta/awesome-python#testing
  27. pytest Plugins Compatibility
    http://plugincompat.herokuapp.com/
  28. Selenium (pro Python)
    https://pypi.org/project/selenium/
  29. Getting Started With Testing in Python
    https://realpython.com/python-testing/
  30. unittest.mock — mock object library
    https://docs.python.org/3­.5/library/unittest.mock.html
  31. mock 2.0.0
    https://pypi.python.org/pypi/mock
  32. An Introduction to Mocking in Python
    https://www.toptal.com/python/an-introduction-to-mocking-in-python
  33. Mock – Mocking and Testing Library
    http://mock.readthedocs.io/en/stable/
  34. Python Mocking 101: Fake It Before You Make It
    https://blog.fugue.co/2016–02–11-python-mocking-101.html
  35. Nauč se Python! – Testování
    http://naucse.python.cz/les­sons/intro/testing/
  36. Flexmock (dokumentace)
    https://flexmock.readthedoc­s.io/en/latest/
  37. Test Fixture (Wikipedia)
    https://en.wikipedia.org/wi­ki/Test_fixture
  38. Mock object (Wikipedia)
    https://en.wikipedia.org/wi­ki/Mock_object
  39. Extrémní programování
    https://cs.wikipedia.org/wi­ki/Extr%C3%A9mn%C3%AD_pro­gramov%C3%A1n%C3%AD
  40. Programování řízené testy
    https://cs.wikipedia.org/wi­ki/Programov%C3%A1n%C3%AD_%C5%99%­C3%ADzen%C3%A9_testy
  41. Pip (dokumentace)
    https://pip.pypa.io/en/stable/
  42. Tox
    https://tox.readthedocs.io/en/latest/
  43. pytest: helps you write better programs
    https://docs.pytest.org/en/latest/
  44. doctest — Test interactive Python examples
    https://docs.python.org/dev/li­brary/doctest.html#module-doctest
  45. unittest — Unit testing framework
    https://docs.python.org/dev/li­brary/unittest.html
  46. Python namespaces
    https://bytebaker.com/2008/07/30/pyt­hon-namespaces/
  47. Namespaces and Scopes
    https://www.python-course.eu/namespaces.php
  48. Stránka projektu Robot Framework
    https://robotframework.org/
  49. GitHub repositář Robot Frameworku
    https://github.com/robotfra­mework/robotframework
  50. Robot Framework (Wikipedia)
    https://en.wikipedia.org/wi­ki/Robot_Framework
  51. Tutoriál Robot Frameworku
    http://www.robotframeworktu­torial.com/
  52. Robot Framework Documentation
    https://robotframework.or­g/robotframework/
  53. Robot Framework Introduction
    https://blog.testproject.i­o/2016/11/22/robot-framework-introduction/
  54. robotframework 3.1.2 na PyPi
    https://pypi.org/project/ro­botframework/
  55. Robot Framework demo (GitHub)
    https://github.com/robotfra­mework/RobotDemo
  56. Robot Framework web testing demo using SeleniumLibrary
    https://github.com/robotfra­mework/WebDemo
  57. Robot Framework for Mobile Test Automation Demo
    https://www.youtube.com/wat­ch?v=06LsU08slP8
  58. Gherkin
    https://cucumber.io/docs/gherkin/
  59. Selenium
    https://selenium.dev/
  60. SeleniumLibrary
    https://robotframework.org/
  61. The Practical Test Pyramid
    https://martinfowler.com/ar­ticles/practical-test-pyramid.html
  62. Acceptance Tests and the Testing Pyramid
    http://www.blog.acceptance­testdrivendevelopment.com/ac­ceptance-tests-and-the-testing-pyramid/
  63. Tab-separated values
    https://en.wikipedia.org/wiki/Tab-separated_values
  64. A quick guide about Python implementations
    https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321
  65. radamsa
    https://gitlab.com/akihe/radamsa
  66. Fuzzing (Wikipedia)
    https://en.wikipedia.org/wiki/Fuzzing
  67. american fuzzy lop
    http://lcamtuf.coredump.cx/afl/
  68. Fuzzing: the new unit testing
    https://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/fuzzing.slide#1
  69. Corpus for github.com/dvyukov/go-fuzz examples
    https://github.com/dvyukov/go-fuzz-corpus
  70. AFL – QuickStartGuide.txt
    https://github.com/google/AF­L/blob/master/docs/QuickStar­tGuide.txt
  71. Introduction to Fuzzing in Python with AFL
    https://alexgaynor.net/2015/a­pr/13/introduction-to-fuzzing-in-python-with-afl/
  72. Writing a Simple Fuzzer in Python
    https://jmcph4.github.io/2018/01/19/wri­ting-a-simple-fuzzer-in-python/
  73. How to Fuzz Go Code with go-fuzz (Continuously)
    https://fuzzit.dev/2019/10/02/how-to-fuzz-go-code-with-go-fuzz-continuously/
  74. Golang Fuzzing: A go-fuzz Tutorial and Example
    http://networkbit.ch/golang-fuzzing/
  75. Fuzzing Python Modules
    https://stackoverflow.com/qu­estions/20749026/fuzzing-python-modules
  76. 0×3 Python Tutorial: Fuzzer
    http://www.primalsecurity.net/0×3-python-tutorial-fuzzer/
  77. fuzzing na PyPi
    https://pypi.org/project/fuzzing/
  78. Fuzzing 0.3.2 documentation
    https://fuzzing.readthedoc­s.io/en/latest/
  79. Randomized testing for Go
    https://github.com/dvyukov/go-fuzz
  80. HTTP/2 fuzzer written in Golang
    https://github.com/c0nrad/http2fuzz
  81. 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
  82. Continuous Fuzzing Made Simple
    https://fuzzit.dev/
  83. Halt and Catch Fire
    https://en.wikipedia.org/wi­ki/Halt_and_Catch_Fire#In­tel_x86
  84. Random testing
    https://en.wikipedia.org/wi­ki/Random_testing
  85. Monkey testing
    https://en.wikipedia.org/wi­ki/Monkey_testing
  86. Fuzzing for Software Security Testing and Quality Assurance, Second Edition
    https://books.google.at/bo­oks?id=tKN5DwAAQBAJ&pg=PR15&lpg=PR15&q=%­22I+settled+on+the+term+fuz­z%22&redir_esc=y&hl=de#v=o­nepage&q=%22I%20settled%20on%20the%20ter­m%20fuzz%22&f=false
  87. libFuzzer – a library for coverage-guided fuzz testing
    https://llvm.org/docs/LibFuzzer.html
  88. fuzzy-swagger na PyPi
    https://pypi.org/project/fuzzy-swagger/
  89. fuzzy-swagger na GitHubu
    https://github.com/namuan/fuzzy-swagger
  90. Fuzz testing tools for Python
    https://wiki.python.org/mo­in/PythonTestingToolsTaxo­nomy#Fuzz_Testing_Tools
  91. A curated list of awesome Go frameworks, libraries and software
    https://github.com/avelino/awesome-go
  92. gofuzz: a library for populating go objects with random values
    https://github.com/google/gofuzz
  93. tavor: A generic fuzzing and delta-debugging framework
    https://github.com/zimmski/tavor
  94. hypothesis na GitHubu
    https://github.com/Hypothe­sisWorks/hypothesis
  95. Hypothesis: Test faster, fix more
    https://hypothesis.works/
  96. Hypothesis
    https://hypothesis.works/ar­ticles/intro/
  97. What is Hypothesis?
    https://hypothesis.works/articles/what-is-hypothesis/
  98. What is Property Based Testing?
    https://hypothesis.works/articles/what-is-property-based-testing/
  99. Databáze CVE
    https://www.cvedetails.com/
  100. Fuzz test Python modules with libFuzzer
    https://github.com/eerimoq/pyfuzzer
  101. Taof – The art of fuzzing
    https://sourceforge.net/pro­jects/taof/
  102. JQF + Zest: Coverage-guided semantic fuzzing for Java
    https://github.com/rohanpadhye/jqf
  103. http2fuzz
    https://github.com/c0nrad/http2fuzz
  104. Demystifying hypothesis testing with simple Python examples
    https://towardsdatascience­.com/demystifying-hypothesis-testing-with-simple-python-examples-4997ad3c5294
  105. Testování
    http://voho.eu/wiki/testovani/
  106. Unit testing (Wikipedia.en)
    https://en.wikipedia.org/wi­ki/Unit_testing
  107. Unit testing (Wikipedia.cz)
    https://cs.wikipedia.org/wi­ki/Unit_testing
  108. Unit Test vs Integration Test
    https://www.youtube.com/wat­ch?v=0GypdsJulKE
  109. TestDouble
    https://martinfowler.com/bli­ki/TestDouble.html
  110. Test Double
    http://xunitpatterns.com/Tes­t%20Double.html
  111. Test-driven development (Wikipedia)
    https://en.wikipedia.org/wiki/Test-driven_development
  112. Acceptance test–driven development
    https://en.wikipedia.org/wi­ki/Acceptance_test%E2%80%93dri­ven_development
  113. Gauge
    https://gauge.org/
  114. Gauge (software)
    https://en.wikipedia.org/wi­ki/Gauge_(software)
  115. PYPL PopularitY of Programming Language
    https://pypl.github.io/PYPL.html
  116. Testing is Good. Pyramids are Bad. Ice Cream Cones are the Worst
    https://medium.com/@fistsOf­Reason/testing-is-good-pyramids-are-bad-ice-cream-cones-are-the-worst-ad94b9b2f05f
  117. Články a zprávičky věnující se Pythonu
    https://www.root.cz/n/python/
  118. PythonTestingToolsTaxonomy
    https://wiki.python.org/mo­in/PythonTestingToolsTaxo­nomy
  119. Top 6 BEST Python Testing Frameworks [Updated 2020 List]
    https://www.softwaretestin­ghelp.com/python-testing-frameworks/
  120. pytest-print 0.1.3
    https://pypi.org/project/pytest-print/
  121. pytest fixtures: explicit, modular, scalable
    https://docs.pytest.org/en/la­test/fixture.html
  122. PyTest Tutorial: What is, Install, Fixture, Assertions
    https://www.guru99.com/pytest-tutorial.html
  123. Pytest – Fixtures
    https://www.tutorialspoin­t.com/pytest/pytest_fixtu­res.htm
  124. Marking test functions with attributes
    https://docs.pytest.org/en/la­test/mark.html
  125. pytest-print
    https://pytest-print.readthedocs.io/en/latest/
  126. Continuous integration
    https://en.wikipedia.org/wi­ki/Continuous_integration
  127. Travis CI
    https://travis-ci.org/
  128. Mutation testing
    https://en.wikipedia.org/wi­ki/Mutation_testing
  129. Články o Hypothesis
    https://news.ycombinator.com/from?si­te=hypothesis.works
  130. Testovací případ
    https://cs.wikipedia.org/wi­ki/Testovac%C3%AD_p%C5%99%C3%AD­pad
  131. Most testing is ineffective
    https://hypothesis.works/
ikonka

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.