Obsah
2. Omezení vstupních hodnot v testech
3. Využití metody filter ve strategiích
4. Další demonstrační příklad – test algoritmu na existenci prvočísel
5. Využití metody map ve strategiích
6. Vytvoření specifických vstupů pro test prvočísel
9. Orákulum a test na predikát pro prvočísla
10. Použití regulárního výrazu definujícího vlastnosti generovaných testovacích dat
11. Další příklady použití regulárních výrazů v testech
12. Generátor dat pro kód používající knihovnu NumPy
14. Pole pravdivostních hodnot
15. Trojrozměrná pole s prvky typu half
16. Bližší specifikace hodnot prvků v generovaných polích
18. Repositář s demonstračními příklady
19. Předchozí články s tématem testování (nejenom) v Pythonu
1. Test výpočtu faktoriálu
V úvodní kapitole si připomeneme, jakým způsobem jsou vlastně testy vytvářené s využitím nástroje Hypothesis konstruovány. Minule jsme v demonstračních příkladech používali algoritmus pro bublinkové řazení a taktéž jednoduchou šifru typu ROT-13, takže si dnes pro změnu ukážeme odlišný algoritmus. Bude se jednat o „školní“ výpočet faktoriálu založený na použití rekurze. S tímto algoritmem jsme se již setkali při popisu nástroje Mypy:
"""Výpočet faktoriálu.""" def factorial(n): """Rekurzivní výpočet faktoriálu.""" assert isinstance(n, int), "Integer expected" if n < 0: return None if n == 0: return 1 result = n * factorial(n-1) assert isinstance(result, int), "Internal error in factorial computation" return result
Jednoduchý test s hypotézou může být založen na faktu, že faktoriál by měl být kladné číslo a současně je jeho hodnota větší nebo rovna vstupu. Jedná se o značně neurčitý test, ale jedná se prozatím o první krok:
"""Jednotkové testy pro výpočet faktoriálu.""" from hypothesis import given from hypothesis.strategies import integers from factorial import factorial @given(integers()) def test_factorial(value): """Jednotkový test pro výpočet faktoriálu.""" assert factorial(value) > 0 assert factorial(value) >= value
Pokus o spuštění testů:
$ pytest -v
však povede k pádu, a to z toho důvodu, že jsme prozatím nepočítali s tím, jak jsou ošetřovány záporné vstupy:
============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/ptisnovs/src/python/testing-in-python/hypothesis/factorial_1/.hypothesis/examples') rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/factorial_1 plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1 collecting ... collected 1 item factorial_test.py::test_factorial FAILED [100%] =================================== FAILURES =================================== ________________________________ test_factorial ________________________________ @given(integers()) > def test_factorial(value): factorial_test.py:10: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ value = -1 @given(integers()) def test_factorial(value): """Jednotkový test pro výpočet faktoriálu.""" > assert factorial(value) > 0 E TypeError: '>' not supported between instances of 'NoneType' and 'int' factorial_test.py:12: TypeError ---------------------------------- Hypothesis ---------------------------------- Falsifying example: test_factorial( value=-1, ) =========================== short test summary info ============================ FAILED factorial_test.py::test_factorial - TypeError: '>' not supported betwe... ============================== 1 failed in 0.07s ===============================
2. Omezení vstupních hodnot v testech
Testy však pochopitelně můžeme upravit, například takovým způsobem, aby se pro kladné hodnoty a nulu používal již v předchozí kapitole popsaný test se dvěma hypotézami a pro hodnoty záporné naopak test na výslednou hodnotu None. Rozsah generovaných vstupů lze velmi snadno omezit s využitím nepovinných parametrů min_value a max_value předávaných konstruktoru integers:
"""Jednotkové testy pro výpočet faktoriálu.""" from hypothesis import given from hypothesis.strategies import integers from factorial import factorial @given(integers(min_value=0)) def test_factorial(value): """Jednotkový test pro výpočet faktoriálu.""" assert factorial(value) > 0 assert factorial(value) >= value @given(integers(max_value=-1)) def test_factorial(value): """Jednotkový test pro výpočet faktoriálu.""" assert factorial(value) is None
Nyní již otestování proběhne s pořádku, i když stále nevíme, zda se vůbec počítá správný faktoriál či zda se pouze například nevrací původní hodnota (pro kladné vstupy):
$ pytest -v ============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/ptisnovs/src/python/testing-in-python/hypothesis/factorial_2/.hypothesis/examples') rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/factorial_2 plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1 collecting ... collected 1 item factorial_test.py::test_factorial PASSED [100%] ============================== 1 passed in 0.11s ===============================
3. Využití metody filter ve strategiích
Už v předchozím článku jsme si ukázali, že při generování vstupních hodnot můžeme použít i metodu filter, která hodnoty omezí na základě nějaké zadané podmínky či podmínek. I mimo testy si můžeme chování této metody ověřit:
from hypothesis.strategies import lists, integers g = lists(integers().filter(lambda x: x > 0 and x % 2 == 0)) for _ in range(10): print(g.example())
Příklad výsledku (bude se lišit v každém spuštění, protože hodnoty jsou generovány pseudonáhodně):
[] [] [] [114, 297635958, 36382594596201776729035817479773596128] [] [] [1584, 20756, 27064] [] [] [6256, 16432]
Můžeme se tedy pokusit generovat celá čísla z plného rozsahu (daného typu v Pythonu) a následně generované hodnoty filtrovat – poprvé použijeme jen čísla větší nebo rovna nule a podruhé pouze záporná čísla:
"""Jednotkové testy pro výpočet faktoriálu.""" from hypothesis import given from hypothesis.strategies import integers from factorial import factorial @given(integers().filter(lambda x: x >= 0)) def test_factorial(value): """Jednotkový test pro výpočet faktoriálu.""" assert factorial(value) > 0 assert factorial(value) >= value @given(integers().filter(lambda x: x < 0)) def test_factorial(value): """Jednotkový test pro výpočet faktoriálu.""" assert factorial(value) is None
Testy by opět měly projít bez chyby, ale možná si povšimnete, že jejich běh bude trvat delší dobu:
$ pytest -v ============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/ptisnovs/src/python/testing-in-python/hypothesis/factorial_3/.hypothesis/examples') rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/factorial_3 plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1 collecting ... collected 1 item factorial_test.py::test_factorial PASSED [100%] ============================== 1 passed in 0.17s ===============================
4. Další demonstrační příklad – test algoritmu na existenci prvočísel
Existuje ovšem i lepší způsob použití metody filter. Můžeme se například pokusit otestovat funkci, která pro zadanou celočíselnou hodnotu vrátí logickou hodnotu True v případě, že vstup je prvočíslem a False v případě, že se jedná o číslo složené, popř. o záporné číslo. Jedna z možných implementací takové funkce může vypadat následovně:
"""Test, zda je vstupní hodnota prvočíslem.""" def is_prime(x): """Test, zda je vstupní hodnota prvočíslem.""" if x == 2: return True if x < 2 or x % 2 == 0: return False return not any(x % y == 0 for y in range(3, int(x**0.5) + 1, 2))
První varianta testů může být založena na zjištění, zda se pro sudé hodnoty (s výjimkou čísla 2) vždy vrátí hodnota False:
"""Jednotkové testy pro funkci is_prime().""" from hypothesis import given from hypothesis.strategies import integers from is_prime import is_prime @given(integers(min_value=3).filter(lambda x: x % 2 == 0)) def test_is_prime_for_even_values(value): """Jednotkový test pro funkci is_prime().""" assert not is_prime(value)
Testy se vstupy a podmínkami generovanými nástrojem Hypothesis by měly proběhnout bez chyby:
$ pytest -v ============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/ptisnovs/src/python/testing-in-python/hypothesis/primes_1/.hypothesis/examples') rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/primes_1 plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1 collecting ... collected 1 item is_prime_test.py::test_is_prime_for_even_values PASSED [100%] ============================== 1 passed in 0.13s ===============================
5. Využití metody map ve strategiích
Předchozí test, v němž jsme filtrovali ze všech kladných čísel větších než 2 ty hodnoty, které jsou sudé, ovšem můžeme i otočit a naopak akceptovat všechna kladná čísla větší než 2 a tyto čísla můžeme násobit dvěma. K tomuto účelu slouží metoda map. Test tedy bude funkci is_prime předávat pseudonáhodně vybrané hodnoty z množiny (4, 6, 8, 10, …):
"""Jednotkové testy pro funkci is_prime().""" from hypothesis import given from hypothesis.strategies import integers from is_prime import is_prime @given(integers(min_value=2).map(lambda x: 2 * x)) def test_is_prime_for_even_values(value): """Jednotkový test pro funkci is_prime().""" assert not is_prime(value)
Testy by v tomto případě opět měly proběhnout bez chyby:
$ pytest -v ============================= test session starts ============================== platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/ptisnovs/src/python/testing-in-python/hypothesis/primes_2/.hypothesis/examples') rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/primes_2 plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1 collecting ... collected 1 item is_prime_test.py::test_is_prime_for_even_values PASSED [100%] ============================== 1 passed in 0.11s ===============================
6. Vytvoření specifických vstupů pro test prvočísel
Pro lepší otestování funkce pro výpočet prvočísel by samozřejmě bylo lepší mít k dispozici lépe vytvořené vstupy. Pro prvních čtyřicet prvočísel existuje polynom sestavený slavným Eulerem – viz též Prime_formulas_and_polynomial_functions. Pokud se za x dosazují hodnoty od 0 do 39, bude výsledkem x-té prvočíslo. Test tedy můžeme (alespoň v malém rozsahu vstupů) napsat i následovně:
"""Jednotkové testy pro funkci is_prime().""" from hypothesis import given from hypothesis.strategies import integers from is_prime import is_prime @given(integers(min_value=0, max_value=39).map(lambda x: x ** 2 + x + 41)) def test_is_prime_for_even_values(value): """Jednotkový test pro funkci is_prime().""" assert is_prime(value)
Otestování by opět mělo proběhnout v pořádku.
7. Strategie builds
Nástroj Hypothesis podporuje i strategii nazvanou builds, která umožňuje, aby byly vstupní hodnoty pro testy vytvářeny (resp. přesněji řečeno generovány) nějakým objektem nebo funkcí. Poněkud umělé použití této strategie můžeme vidět v dalším demonstračním příkladu, ve kterém se testovaná funkce is_prime volá s náhodně vybranými prvočísly. Z časových důvodů je test omezen na prvních sto prvočísel, ovšem pochopitelně je možné tuto hodnotu snadno zvýšit. Prvočísla jsou vypočtena pomocnou funkcí nazvanou generate_nth_prime, která si mezivýsledky (již vypočtená menší prvočísla) ukládá do mezipaměti reprezentovanou seznamem pojmenovaným cache. Pochopitelně se ovšem v žádném případě nejedná o rychlé ani paměťově efektivní řešení:
"""Jednotkové testy pro funkci is_prime().""" from hypothesis import given from hypothesis.strategies import integers, builds from is_prime import is_prime cache = [2, 3] def generate_nth_prime(n): """Vygenerování n-tého prvočísla (lze získat z cache).""" if n < len(cache): return cache[n] x = cache[-1] while len(cache) < n: # zde je pochopitelně vhodné použít jiný algoritmus, než ten, který je testován!!! if not any(x % y == 0 for y in range(3, int(x**0.5) + 1, 2)): cache.append(x) x += 2 return cache[-1] @given(builds(generate_nth_prime, integers(min_value=1, max_value=100))) def test_is_prime_for_even_values(value): """Jednotkový test pro funkci is_prime().""" assert is_prime(value)
8. Testy s orákulem
O testech s orákulem jsme se zmínili minule. V případě, že není možné orákulum implementovat formou algoritmu, můžeme si v mnoha případech vypomoci předpočítanými hodnotami. A tento přístup je ukázán i v dalším demonstračním příkladu, v němž testujeme funkci pro výpočet faktoriálu oproti hodnotám, které byly získány dopředu a především jiným způsobem, než testovaným algoritmem (v tomto případě jde o dosti umělý příklad, který vlastně vůbec nevyžaduje použití Hypothesis):
"""Jednotkové testy pro výpočet faktoriálu.""" from hypothesis import given from hypothesis.strategies import lists, integers from factorial import factorial precomputed = [ 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000, 355687428096000, 6402373705728000, 121645100408832000, 2432902008176640000, 51090942171709440000, 1124000727777607680000, 25852016738884976640000, 620448401733239439360000, 15511210043330985984000000, 403291461126605635584000000, 10888869450418352160768000000, 304888344611713860501504000000, 8841761993739701954543616000000, 265252859812191058636308480000000, ] @given(integers(min_value=1, max_value=30)) def test_factorial(value): """Jednotkový test pro výpočet faktoriálu.""" assert factorial(value) == precomputed[value] @given(integers().filter(lambda x: x < 0)) def test_factorial(value): """Jednotkový test pro výpočet faktoriálu.""" assert factorial(value) is None
9. Orákulum a test na predikát pro prvočísla
Naprosto stejným způsobem ovšem můžeme získat tabulku s prvočísly a opět posunout Hypothesis směrem ke klasickým jednotkovým testům řízeným tabulkami:
"""Jednotkové testy pro funkci is_prime().""" from hypothesis import given from hypothesis.strategies import integers, builds from is_prime import is_prime precomputed = [ 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 ] @given(integers(min_value=1, max_value=100)) def test_is_prime(value): """Jednotkový test pro funkci is_prime().""" assert is_prime(value) == (value in precomputed)
Lepší je ovšem (pokud je to pochopitelně možné – a nyní to možné je) namísto statické tabulky použít tabulku vypočtenou až do zadaného limitu. K tomuto účelu můžeme použít například algoritmus, s nímž jsme se již v tomto seriálu setkali:
"""Jednotkové testy pro funkci is_prime().""" from hypothesis import given from hypothesis.strategies import integers, builds from is_prime import is_prime # originální kód lze nalézt na adrese: # http://www.rosettacode.org/wiki/Sieve_of_Eratosthenes#Odds-only_version_of_the_array_sieve_above def 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] LIMIT = 1000 precomputed = primes(LIMIT) @given(integers(min_value=1, max_value=LIMIT)) def test_is_prime(value): """Jednotkový test pro funkci is_prime().""" assert is_prime(value) == (value in precomputed)
10. Použití regulárního výrazu definujícího vlastnosti generovaných testovacích dat
Nástroj Hypothesis nabízí i velmi mocný nástroj – možnost použití regulárního výrazu, který definuje vlastnosti generovaných testovacích dat, v tomto případě řetězců nebo polí bajtů. Všechny řetězce/pole bajtů budou regulárním výrazem akceptovány. Tuto možnost, která je velmi užitečná (generování URL, e-mailových adres atd. atd.), si nejprve ukážeme jen na velmi prostém příkladu, který ani není testem:
from hypothesis.strategies import from_regex g = from_regex(r"#[a-fA-F0-9]{6}") for _ in range(20): print(g.example())
Tento příklad vygeneruje dvacet řetězců, které obsahují část odpovídající barvě zakódované v šestici hexa číslic, tj. ve formátu, který se mj. prosadil i ve světě HTML a CSS:
#000000 #000000 #e00000 #A00000 #000000 #F00000 #200000??? #d00000 #d00000 #a00000 #A00000 fdsa#a00000 #400000 #fe0000 #000000 #e00000 #0b0000 #800000 ????????????????????#Ce10DE???? #000000
Povšimněte si, že řetězce obsahují i další znaky, což je ovšem ve skutečnosti v pořádku, protože i takové řetězce (nebo jejich části) budou regulárním výrazem akceptovány.
Pokud budeme chtít vytvářet pouze řetězce akceptované regulárním výrazem jako celek, je nutné použít parametr fullmatch=True:
from hypothesis.strategies import from_regex g = from_regex(r"#[a-fA-F0-9]{6}", fullmatch=True) for _ in range(20): print(g.example())
Nyní se již vytváří řetězce obsahující kódy různých barev, což je pro testování ideální:
#c00000 #fF3D78 #a00000 #c3C4Eb #000000 #B00000 #000000 #400000 #DC62c5 #502B12 #d6bB2B #fDaAFa #b00000 #F00000 #000000 #59a7F9 #000000 #0E0000 #000000 #bCdB0E
11. Další příklady použití regulárních výrazů v testech
Podívejme se ještě na několik příkladů, které jsou založeny na regulárních výrazech a taktéž na funkci from_regex. Následující regulární výraz je poměrně často používán na ověření, zda řetězec obsahuje platnou e-mailovou adresu:
[^@\s]+@[^@\s]+\.[a-zA-Z0-9]+$
Můžeme tedy jít opačným směrem a použít tento výraz pro vygenerování e-mailových adres:
from hypothesis.strategies import from_regex g = from_regex(r"[^@\s]+@[^@\s]+\.[a-zA-Z0-9]+$", fullmatch=True) for _ in range(20): print(g.example())
S těmito výsledky:
0@0.0 -%????@????#.RHb 0@0.0 0@0.0 0@0.0abc 0@0.0 '@0.0 0@0.0 0@0.0 .@0.0 0@0.0 %'????)鋕@"!&.gkQsxQ55 (@0.0 '#????+,-????$@????%????????,+-.SrQm ????????????@????!&.8f7 ????@0.0 (@0.0 (@0.0 0@0.0 0@0.0
Pokud vám nevyhovuje, že se v adrese může objevit prakticky jakýkoli nebílý znak z Unicode, lze omezit sadu povolených znaků pouze na ASCII, a to nám již známým způsobem – pomocí metody filter:
from hypothesis.strategies import from_regex g = from_regex(r"[^@\s]+@[^@\s]+\.[a-zA-Z0-9]+$", fullmatch=True).filter(lambda s:all(ord(c) < 128 for c in s)) for _ in range(20): print(g.example())
S výsledky:
0@0.0 0@0.0 +@0.0 0@0.0 /,@+.Uf 0@0.0 0@0.0 0@0.0 0@0.0 *@0.0 0@0.0 /@0.0 +@0.0 0@0.0 *@0.0 0@0.0 00@0.0 0@0.0 0@0.0 0@0.0
12. Generátor dat pro kód používající knihovnu NumPy
Programovací jazyk Python se velmi často používá společně s knihovnou NumPy, s níž jsme se ostatně na stránkách Rootu již seznámili. A právě z tohoto důvodu obsahuje nástroj Hypothesis možnost tvorby polí (ND-Array) specifikované velikosti, tvaru (shape) a typů prvků. V následujícím příkladu je ukázáno, jak lze vygenerovat desetiprvkové vektory s prvky typu int8, což jsou osmibitová celá čísla se znaménkem:
import numpy as np from hypothesis.extra.numpy import arrays g = arrays(np.int8, 10, elements=None) for _ in range(10): print(g.example())
Výsledek činnosti předchozího skriptu – vektory použitelné při testování:
[0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [-21 -21 -51 103 -21 -21 107 119 -1 -21] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [1 1 1 1 1 1 1 1 1 1] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [116 -58 116 116 109 116 116 116 116 116] [-1 -1 -1 -1 -1 -1 -1 -1 -1 -1]
13. Vícerozměrná pole
U polí je možné kromě počtu prvků specifikovat i tvar (shape) pole. V dalším příkladu mají pole tvar matice se čtyřmi řádky a třemi sloupci, takže jejich tvar je určen n-ticí (4,3):
import numpy as np from hypothesis.extra.numpy import arrays g = arrays(np.float, (4,3), elements=None) for _ in range(10): print(g.example()) print()
Příklad několika výsledků:
[[0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] [[0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] [[0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] [[2.89039723e-147 2.89039723e-147 2.89039723e-147] [2.89039723e-147 2.89039723e-147 2.89039723e-147] [2.89039723e-147 2.89039723e-147 2.89039723e-147] [2.89039723e-147 2.89039723e-147 2.89039723e-147]] [[0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] [[0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] [[0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] [[0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] [[-1.79769313e+308 -1.79769313e+308 -1.79769313e+308] [-1.79769313e+308 -1.79769313e+308 -3.08532025e+016] [-1.79769313e+308 -1.79769313e+308 -1.79769313e+308] [-1.79769313e+308 -2.84042690e+107 -1.79769313e+308]] [[-5.38016682e+016 -5.38016682e+016 -1.10000000e+000] [-5.38016682e+016 1.32684804e+016 -5.38016682e+016] [-5.38016682e+016 nan -5.38016682e+016] [ 1.10497728e+163 -5.38016682e+016 -5.38016682e+016]]
14. Pole pravdivostních hodnot
Vzhledem k tomu, že knihovna Numpy podporuje i typ „pravdivostní hodnota“ reprezentovaný identifikátorem np.bool, lze generovat i pole obsahující náhodné hodnoty tohoto typu. V tomto konkrétním případě pole o rozměrech 10×10 prvků:
import numpy as np from hypothesis.extra.numpy import arrays g = arrays(np.bool, (10,10)) print(g.example())
V první iteraci většinou získáme pole s poněkud nudným obsahem, ovšem v reálných testech pochopitelně budou použita pole odlišná:
[[False False False False False False False False False False] [False False False False False False False False False False] [False False False False False False False False False False] [False False False False False False False False False False] [False False False False False False False False False False] [False False False False False False False False False False] [False False False False False False False False False False] [False False False False False False False False False False] [False False False False False False False False False False] [False False False False False False False False False False]]
15. Trojrozměrná pole s prvky typu half
Pro algoritmy ML a AI se někdy používá i datový typ half floating point, o němž jsme se zmiňovali v článku Brain Floating Point – nový formát uložení čísel pro strojové učení a chytrá čidla. Knihovna NumPy umožňuje práci i s tímto typem, resp. přesněji řečeno s N-dimenzionálními poli, které prvky tohoto typu obsahují:
import numpy as np from hypothesis.extra.numpy import arrays g = arrays(np.half, (2,3,4), elements=None, unique=True) for _ in range(10): print(g.example()) print()
Tento jednoduchý demonstrační příklad vygeneruje trojrozměrné matice jejichž prvky opět obsahují i „speciální“ hodnoty:
[[[-1.100e+00 0.000e+00 -inf 1.192e-07] [ nan -3.333e-01 nan nan] [ 1.500e+00 1.100e+00 nan inf]] [[-1.001e-05 5.000e-01 nan -1.500e+00] [-1.192e-07 -2.000e+00 -1.900e+00 -5.000e-01] [ 2.000e+00 nan 3.333e-01 nan]]] [[[ nan -1.000e+00 1.192e-07 1.900e+00] [ nan 1.001e-05 inf -0.000e+00] [ nan -5.000e-01 -3.333e-01 nan]] [[-1.500e+00 -inf nan 1.500e+00] [-1.192e-07 nan 2.000e+00 nan] [ nan nan nan nan]]] [[[-0.000e+00 nan inf nan] [ -inf nan nan nan] [ 1.900e+00 5.000e-01 nan nan]] [[ nan nan -5.000e-01 1.100e+00] [ nan 1.000e+00 -3.333e-01 -1.001e-05] [ nan nan nan 1.500e+00]]] [[[-0.000e+00 1.500e+00 -1.192e-07 nan] [ 1.100e+00 1.900e+00 inf nan] [ nan nan 5.000e-01 nan]] [[ -inf nan 2.000e+00 -1.001e-05] [ nan nan nan 1.192e-07] [-3.333e-01 nan -2.000e+00 -5.000e-01]]] [[[-1.900e+00 0.000e+00 inf 1.100e+00] [ 3.333e-01 -5.000e-01 nan -1.100e+00] [ nan -1.192e-07 1.192e-07 nan]] [[ nan -2.000e+00 nan 2.000e+00] [-1.001e-05 nan -inf 1.900e+00] [ nan 1.001e-05 -3.333e-01 nan]]] [[[ nan inf 1.100e+00 0.000e+00] [-1.500e+00 nan -inf -1.192e-07] [ 2.000e+00 nan -1.000e+00 nan]] [[ nan nan nan 1.001e-05] [ nan -3.333e-01 1.192e-07 3.333e-01] [ nan nan -1.001e-05 nan]]] [[[ inf 3.333e-01 1.192e-07 nan] [-3.333e-01 2.000e+00 -0.000e+00 nan] [ -inf nan -1.001e-05 -5.000e-01]] [[ nan 1.500e+00 nan nan] [ nan 5.000e-01 1.001e-05 nan] [ nan -1.500e+00 1.100e+00 nan]]] [[[ -inf 0.5 0. inf] [-1. nan 1. 2. ] [-0.3333 nan -2. -1.9 ]] [[ 1.1 nan 1.9 nan] [ nan -1.5 nan nan] [ 1.5 nan nan 0.3333]]] [[[ -inf 3.333e-01 1.001e-05 1.192e-07] [-1.000e+00 inf 0.000e+00 1.500e+00] [-1.001e-05 nan -1.192e-07 nan]] [[ nan -2.000e+00 nan nan] [-1.900e+00 5.000e-01 nan -3.333e-01] [ nan nan 1.900e+00 1.000e+00]]] [[[ nan -1.0e-05 -0.0e+00 -1.2e-07] [ nan nan nan -1.0e+00] [ 1.9e+00 1.5e+00 -inf inf]] [[ nan nan -5.0e-01 nan] [ 1.0e+00 1.1e+00 nan nan] [ nan 1.2e-07 nan 2.0e+00]]]
16. Bližší specifikace hodnot prvků v generovaných polích
V mnoha případech je nutné blíže specifikovat hodnoty prvků v pseudonáhodně generovaných polích. I to je možné, i když s určitými omezeními. Podívejme se na následující demonstrační příklad, v němž jsou generovány trojrozměrné matice s prvky typu celé číslo, ovšem hodnoty prvků jsou navíc omezeny tím, že musí být v rámci jedné matice unikátní a současně musí být v rozsahu 0 až 100:
import numpy as np from hypothesis.strategies import integers from hypothesis.extra.numpy import arrays g = arrays(np.int32, (2,3,4), elements=integers(0, 100), unique=True) for _ in range(10): print(g.example()) print()
S výsledky:
[[[ 88 60 59 99] [ 78 3 83 100] [ 26 98 95 8]] [[ 74 47 66 48] [ 73 42 70 31] [ 14 75 62 24]]] [[[27 90 54 48] [ 0 3 36 89] [37 68 60 19]] [[31 75 16 97] [10 29 17 70] [84 82 43 85]]] [[[38 89 81 27] [69 74 87 46] [95 4 67 23]] [[99 83 35 11] [ 3 10 54 58] [36 20 75 50]]] [[[52 74 5 71] [89 15 18 40] [ 0 86 45 97]] [[39 69 96 35] [25 8 66 50] [43 51 26 32]]] ... ... ... [[46 39 90 31] [22 4 69 57] [49 81 68 91]]]
17. Závěr
Ve dvojici článků jsme si ukázali některé základní způsoby použití knihovny Hypothesis pro vylepšení pokrytí stavového prostoru bloků (funkcí, metod, objektů) jednotkovými testy. Tento nástroj prozatím není dokonalý a některé jeho vlastnosti pravděpodobně příliš neoceníte – například je problemativké (nikoli však nemožné) pracovat s rekurzivními datovými strukturami či generovat obecnější struktury na základě jejich schématu. K tomuto účelu je podle mého názoru vhodnější použít specializovanější knihovny, z nichž některé si popíšeme v navazujících částech tohoto seriálu.
18. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/testing-in-python. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady a jejich části, které naleznete v následující tabulce:
19. Předchozí články s tématem testování (nejenom) v Pythonu
Tématem testování jsme se již na stránkách Rootu několikrát zabývali. Jedná se mj. o následující články:
- Použití Pythonu pro tvorbu testů: od jednotkových testů až po testy UI
https://www.root.cz/clanky/pouziti-pythonu-pro-tvorbu-testu-od-jednotkovych-testu-az-po-testy-ui/ - Použití Pythonu pro tvorbu testů: použití třídy Mock z knihovny unittest.mock
https://www.root.cz/clanky/pouziti-pythonu-pro-tvorbu-testu-pouziti-tridy-mock-z-knihovny-unittest-mock/ - Použití nástroje pytest pro tvorbu jednotkových testů a benchmarků
https://www.root.cz/clanky/pouziti-nastroje-pytest-pro-tvorbu-jednotkovych-testu-a-benchmarku/ - 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/ - 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
- 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/