Testování aplikací s využitím nástroje Hypothesis

9. 6. 2020
Doba čtení: 36 minut

Sdílet

 Autor: Depositphotos
Dnešní část je opět věnována pokrytí stavového prostoru testovaných jednotek. Zatímco minule jsme si ukázali, jak tento stavový prostor zmenšit, dnes si naopak ukážeme, jak ho pokrýt automaticky vytvářenými testy.

Obsah

1. Testování aplikací s využitím nástroje Hypothesis

2. Jak testování probíhá

3. Malá odbočka – Hoarého logika

4. Jednoduchý příklad – implementace bublinkového řazení

5. Vytvoření klasických jednotkových testů

6. Použití Hypothesis a orákula

7. Zdánlivé vylepšení algoritmu bublinkového řazení

8. Zavedení chyby do implementace algoritmu

9. Výsledek testů vytvořených nástrojem Hypothesis

10. Specifikace minimální a maximální velikosti vstupů

11. Vylepšení podmínky při neexistenci referenční implementace

12. Jednoduchá šifra typu ROT13

13. Otestování jednoduché šifry

14. Zanesení chyby do algoritmu šifrování

15. Přesnější specifikace podoby generovaných textových dat

16. Metody map a filter

17. Kombinace více strategií

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. Testování aplikací s využitím nástroje Hypothesis

V předchozí části seriálu o tvorbě testů s využitím programovacího jazyka Python jsme si řekli, jakým způsobem je možné zmenšit „stavový prostor“ testované jednotky (tedy funkce, metody či celého objektu) a tím pádem i zmenšit počet testů, které je nutné explicitně vytvořit. Pro zmenšení oblasti stavového prostoru jsme použili nástroj Mypy, který umožňuje zkontrolovat (nepovinné) typové deklarace, jenž mohou být ve zdrojových kódech Pythonu použity. Jedná se o relativně novou vlastnost zavedenou v rámci Pythonu 3.5 a později ještě lépe stabilizovanou (verze 3.5 sice vyšla již před pěti lety, ovšem na příkladu Pythonu 2 je patrné, že nové verze nebývají vždy přijímány příliš rychle). Existuje ovšem i opačný přístup k tvorbě testů – nechat si na základě zadaných pravidel a vzorů nechat testy automaticky vygenerovat, ideálně takovým způsobem, aby se ve vygenerovaných testech projevily i různé mezní případy. A právě na tomto přístupu je založen nástroj nazvaný Hypothesis, kterým se budeme zabývat dnes.

Nástroj Hypothesis je ovšem velmi užitečný i při dalších činnostech, nejenom jako pomocník při tvorbě testů. Díky tomu, že lze použít takzvané orákulum (viz navazující kapitoly), je umožněno, aby byl Hypothesis použit například při refaktoringu či při optimalizacích algoritmů – postačuje totiž mít dvě implementace algoritmu (neoptimalizovanou/nerefaktorovanou a novou) a ty si nechat navzájem prověřit, pochopitelně opět s využitím mezních případů. Podle mého názoru se jedná o velmi užitečný a především praktický přístup.

Před pročítáním dalších kapitol je vhodné si Hypothesis nainstalovat, a to klasicky s využitím nástroje pip či pip3:

$ pip3 install --user hypothesis
 
Collecting hypothesis
  Downloading https://files.pythonhosted.org/packages/99/27/4a3fd8eb6e121e6769bf83a3c1647bc1daab586ac7c70fcf93c4756c51f3/hypothesis-5.16.0-py3-none-any.whl (294kB)
Collecting sortedcontainers<3.0.0,>=2.1.0 (from hypothesis)
  Downloading https://files.pythonhosted.org/packages/13/f3/cf85f7c3a2dbd1a515d51e1f1676d971abe41bba6f4ab5443240d9a78e5b/sortedcontainers-2.1.0-py2.py3-none-any.whl
Collecting attrs>=19.2.0 (from hypothesis)
  Downloading https://files.pythonhosted.org/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Installing collected packages: sortedcontainers, attrs, hypothesis
Successfully installed attrs-19.3.0 hypothesis-5.16.0 sortedcontainers-2.1.0

2. Jak testování probíhá

Pojďme si nyní ukázat, jak vlastně testování s využitím nástroje Hypothesis probíhá. Nejdříve z pohledu vývojáře (či obecněji tvůrce testů):

  1. Uživatel nejdříve popíše, jak má vypadat validní vstup či vstupy do testované jednotky.
  2. Dále napíše test, který by pro tento vstup měl projít bez chyby.

Z pohledu nástroje Hypothesis:

  1. Automaticky vytvoří test pro jednotlivé testovací případy založené na popisu validního vstupu.
  2. Spustí vytvořený test pro jednotlivé testovací případy.
  3. Sesbírá vstupy u těch testů, které z nějakého důvodu zhavarovaly.
  4. Nakonec se pokusí najít minimální vstup způsobující chybu.
Poznámka: asi jste si povšimli, že princip práce nástroje Hypothesis se do značné míry podobá klasickým fuzzerům, o nichž jsme se již zmínili v předchozích článcích. A skutečně – na Hypothesis se můžeme dívat jako na specializovanou generaci fuzzerů s deklarativně omezenou množinou testovacích dat.

Při generování testovacích dat se používají takzvané strategie. Ty si můžeme snadno odzkoušet, a to ještě před vytvořením testů. Na samotné strategie se totiž můžeme dívat jako na generátory „fuzzy“ dat se zadanými vlastnosti – typem, lze použít filtraci, funkci map atd.

Vygenerování deseti sad testovacích dat popsaných takto: „seznam obsahující celá čísla“:

from hypothesis.strategies import lists, integers
 
g = lists(integers())
 
for _ in range(10):
    print(g.example())

Příklad výsledků:

[-74, 1304181783, -1693807871, -10980, -4652, -16732]
[-30506, 23947, 61]
[0]
[101, -27349, 7493]
[-16316, -121, 21307]
[]
[]
[0]
[0]
[339202945]
Poznámka: seznamy budou pseudonáhodné a po každém spuštění jiné, pokud si ovšem nezvolíte konstantní semínko (seed), což je ostatně velmi užitečné například na CI.

Vygenerování deseti sad testovacích dat, tentokrát řetězců o minimální délce pěti znaků a maximální délce deseti znaků:

from hypothesis.strategies import text
 
g = text(min_size=5, max_size=10)
 
for _ in range(10):
    print(g.example())
Poznámka: výsledky neuvádím, protože Hypothesis v tomto případě použije různé znaky z celého rozsahu Unicode.

Generování textových vstupů lze ovšem dále omezit, například na tisknutelné znaky:

from string import printable
from hypothesis.strategies import text
 
g = text(printable, min_size=5, max_size=10)
 
for _ in range(10):
    print(g.example())
Poznámka: samozřejmě se prozatím jedná o primitivní ukázky, ovšem v souvislosti s testy popsanými v rámci dalších kapitol začne být zřejmé, proč je tento přístup užitečný.

3. Malá odbočka – Hoarého logika

Sice to tak nemusí na první pohled vypadat, ale jak Hypothesis, tak i obecnější BDD (Behavior-driven development) jsou založeny na Hoarého logice (nebo též Hoarého trojici). Ta je založena na trojici {P}C{Q}, kde se symbolem P označuje podmínka platná před spuštěním testovaného bloku, Q je podmínka, která musí platit po dokončení bloku a C představuje spuštěný (testovaný) blok.

Konkrétně může test vypadat následovně:

@given(lists(integers()))
def test_bubble_sort(alist):
    l = bubble_sort(alist)
    assert l == sorted(alist)

P je představován prvním řádkem s dekorátorem @given, C je spouštěný blok, tedy třetí řádek a Q je podmínka zapsaná na řádku posledním.

Ještě lépe je trojice vidět na BDD testech, kde jsou jednotlivé prvky trojice zapsány větami za slovy Given, When a Then:

Feature: Adder test
 
  Scenario: Check the function add()
    Given The function add is callable
    When I call function add with arguments 1 and 2
    Then I should get 3 as a result

4. Jednoduchý příklad – implementace bublinkového řazení

Použití nástroje Hypothesis si nejdříve ukážeme na jednoduchém (a dosti naivně implementovaném) algoritmu pro bublinkové řazení. Pochopitelně se nejedná o algoritmus vhodný pro praktické nasazení (snad kromě speciálních případů), ovšem svému účelu v kontextu tohoto článku vyhovuje. Naivní bublinkové třídění bez detekce toho, že je seznam již setříděn, může vypadat takto:

"""Implementace naivního algoritmu bublinkového řazení."""
 
 
def bubble_sort(alist):
    """Implementace naivního algoritmu bublinkového řazení."""
    for i in range(len(alist)-1, 0, -1):
        for j in range(0, i):
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]
    return alist
 
 
if __name__ == '__main__':
    print(bubble_sort([]))
    print(bubble_sort([1]))
 
    print(bubble_sort([1, 2]))
    print(bubble_sort([2, 1]))
 
    print(bubble_sort([1, 2, 3, 4]))
    print(bubble_sort([1, 2, 4, 3]))
    print(bubble_sort([1, 4, 3, 2]))
    print(bubble_sort([4, 3, 2, 1]))
 
    print(bubble_sort([1, 5, 4, 3, 2]))
    print(bubble_sort([5, 4, 3, 2, 1]))

Součástí modulu je i několik příkladů použití funkce bubble_sort, takže si můžeme ověřit její chování:

[]
[1]
[1, 2]
[1, 2]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

5. Vytvoření klasických jednotkových testů

Samozřejmě je možné si napsat běžné jednotkové testy, které chování funkce bubble_sort ověří, ovšem pouze na zadaných datech:

"""Test naivního algoritmu bublinkového řazení."""
 
from bubble_sort import bubble_sort
 
 
def test_bubble_sort_empty_input():
    """Test naivního algoritmu bublinkového řazení."""
    assert bubble_sort([]) == []
 
 
def test_bubble_sort_one_item():
    """Test naivního algoritmu bublinkového řazení."""
    assert bubble_sort([1]) == [1]
 
 
def test_bubble_sort_two_items():
    """Test naivního algoritmu bublinkového řazení."""
    assert bubble_sort([1, 2])
    assert bubble_sort([2, 1])
 
 
def test_bubble_sort_four_items():
    """Test naivního algoritmu bublinkového řazení."""
    assert bubble_sort([1, 2, 3, 4])
    assert bubble_sort([1, 2, 4, 3])
    assert bubble_sort([1, 4, 3, 2])
    assert bubble_sort([4, 3, 2, 1])
 
 
def test_bubble_sort_five_items():
    """Test naivního algoritmu bublinkového řazení."""
    assert bubble_sort([1, 5, 4, 3, 2])
    assert bubble_sort([5, 4, 3, 2, 1])

Takto naprogramované testy můžeme spustit běžným způsobem:

$ pytest -v

S výsledky:

============================= test session starts ==============================
platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_1/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_1
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 5 items
 
bubble_sort_test.py::test_bubble_sort_empty_input PASSED                 [ 20%]
bubble_sort_test.py::test_bubble_sort_one_item PASSED                    [ 40%]
bubble_sort_test.py::test_bubble_sort_two_items PASSED                   [ 60%]
bubble_sort_test.py::test_bubble_sort_four_items PASSED                  [ 80%]
bubble_sort_test.py::test_bubble_sort_five_items PASSED                  [100%]
 
============================== 5 passed in 0.02s ===============================

6. Použití Hypothesis a orákula

Nyní se pokusíme pro otestování algoritmu bublinkového řazení použít nástroj Hypothesis. V tomto případě bude testovaná podmínka jednoduchá, a to z toho důvodu, že máme k dispozici vhodné orákulum, což je jen květnaté označení mechanismu určujícího, zda testovaná funkce prošla testem či nikoli. V případě algoritmu řazení je orákulem jiný – již prověřený – algoritmus řazení a ten existuje ve standardní knihovně jazyka Python pod jménem sorted. Celý test lze zapsat takto:

"""Test naivního algoritmu bublinkového řazení."""
 
from hypothesis import given
from hypothesis.strategies import lists, integers
 
from bubble_sort import bubble_sort
 
 
@given(lists(integers()))
def test_bubble_sort(alist):
    assert bubble_sort(alist) == sorted(alist)

Celý zápis je vlastně velmi jednoduchý. Po importech nutných funkcí a dekorátorů následuje zápis testu podle specifikací vyžadovaných nástrojem Pytest. V něm je použit dekorátor, který určuje, že testovací funkce test_bubble_sort bude postupně volána a bude jí předáván vygenerovaný seznam celých čísel. Ve funkci ověříme, zda výsledek řazení pomocí bubble_sort odpovídá výsledku řazení pomocí ověřeného algoritmu (sorted).

Takto zapsaný test opět spustíme standardním způsobem:

$ pytest -v

S výsledky:

============================= test session starts ==============================
platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_2/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_2
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
bubble_sort_test.py::test_bubble_sort PASSED                             [100%]
 
============================== 1 passed in 0.23s ===============================

Můžeme si nechat zobrazit i podrobnější statistiku o operacích provedených nástrojem Hypothesis:

$ pytest -v --hypothesis-show-statistics
 
============================= 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/bubble_sort_2/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_2
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
bubble_sort_test.py::test_bubble_sort PASSED                             [100%]
============================ Hypothesis Statistics =============================
 
bubble_sort_test.py::test_bubble_sort:
 
  - during reuse phase (0.00 seconds):
    - Typical runtimes: < 1ms, ~ 54% in data generation
    - 2 passing examples, 0 failing examples, 0 invalid examples
 
  - during generate phase (0.18 seconds):
    - Typical runtimes: 0-1 ms, ~ 75% in data generation
    - 98 passing examples, 0 failing examples, 0 invalid examples
 
  - Stopped because settings.max_examples=100
 
 
============================== 1 passed in 0.21s ===============================

7. Zdánlivé vylepšení algoritmu bublinkového řazení

V minulosti existovala celá řada pokusů o vylepšení algoritmu bublinkového řazení, aby se zlepšila jeho dosti špatná časová složitost O(n2). Existují samozřejmě ještě horší algoritmy, například Bogosort, ovšem ty jsou schválně navrženy takovým způsobem, aby měly špatnou časovou složitost. Jedním z vylepšení (či lépe řečeno pseudovylepšení) je použití čítače, který testuje, kolik operací se maximálně provede. Ve skutečnosti se jedná o kód, který složitost nevylepší, ale jeho přidání nám umožní později do algoritmu zanést chybu:

"""Implementace naivního algoritmu bublinkového řazení."""
 
 
def bubble_sort(alist):
    """Implementace naivního algoritmu bublinkového řazení."""
    cnt = len(alist)*(len(alist)-1)/2
    for i in range(len(alist)-1, 0, -1):
        for j in range(0, i):
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]
            cnt -= 1
            if cnt == 0:
                return alist
    return alist
 
 
if __name__ == '__main__':
    print(bubble_sort([]))
    print(bubble_sort([1]))
 
    print(bubble_sort([1, 2]))
    print(bubble_sort([2, 1]))
 
    print(bubble_sort([1, 2, 3, 4]))
    print(bubble_sort([1, 2, 4, 3]))
    print(bubble_sort([1, 4, 3, 2]))
    print(bubble_sort([4, 3, 2, 1]))
 
    print(bubble_sort([1, 5, 4, 3, 2]))
    print(bubble_sort([5, 4, 3, 2, 1]))

Ověření funkčnosti (když už ne vylepšení) upraveného algoritmu:

============================= 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/bubble_sort_3/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_3
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
bubble_sort_test.py::test_bubble_sort PASSED                             [100%]
 
============================== 1 passed in 0.18s ===============================

8. Zavedení chyby do implementace algoritmu

Nyní do algoritmu řazení schválně zaneseme chybu – ukončíme celý proces o jeden krok dříve, což může vést k tomu, že v některých případech nebude seznam zcela setříděn (typicky pokud je počáteční seznam otočený):

cnt = len(alist)*(len(alist)-1)/2-1

Upravený (resp. přesněji řečeno rozbitý) zdrojový kód:

"""Implementace naivního algoritmu bublinkového řazení."""
 
 
def bubble_sort(alist):
    """Implementace naivního algoritmu bublinkového řazení."""
    cnt = len(alist)*(len(alist)-1)/2-1
    for i in range(len(alist)-1, 0, -1):
        for j in range(0, i):
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]
            cnt -= 1
            if cnt == 0:
                return alist
    return alist
 
 
if __name__ == '__main__':
    print(bubble_sort([]))
    print(bubble_sort([1]))
 
    print(bubble_sort([1, 2]))
    print(bubble_sort([2, 1]))
 
    print(bubble_sort([1, 2, 3, 4]))
    print(bubble_sort([1, 2, 4, 3]))
    print(bubble_sort([1, 4, 3, 2]))
    print(bubble_sort([4, 3, 2, 1]))
 
    print(bubble_sort([1, 5, 4, 3, 2]))
    print(bubble_sort([5, 4, 3, 2, 1]))

Z výsledků je patrné, že se skutečně v některých případech sekvence neseřadí. Tyto výsledky jsou zvýrazněny:

[]
[1]
[1, 2]
[1, 2]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
[2, 1, 3, 4]
[1, 2, 3, 4, 5]
[2, 1, 3, 4, 5]

9. Výsledek testů vytvořených nástrojem Hypothesis

A nyní přijde na řadu zjištění, jak dobře (či jak špatně) dokáže námi rozbitý algoritmus otestovat nástroj Hypothesis. Samotné testy ponecháme beze změny:

"""Test naivního algoritmu bublinkového řazení."""
 
from hypothesis import given
from hypothesis.strategies import lists, integers
 
from bubble_sort import bubble_sort
 
 
@given(lists(integers()))
def test_bubble_sort(alist):
    assert bubble_sort(alist) == sorted(alist)

Ovšem v ideálním případě by měla být chyba odhalena i s tímto jednoduchým (v podstatě třířádkovým) testem:

$ pytest -v --hypothesis-show-statistics

Chyba je odhalena relativně rychle, a to konkrétně na sekvenci se třemi prvky:

 
============================= 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/bubble_sort_4/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_4
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
bubble_sort_test.py::test_bubble_sort FAILED                             [100%]
 
=================================== FAILURES ===================================
_______________________________ test_bubble_sort _______________________________
 
    @given(lists(integers()))
>   def <strong>test_bubble_sort</strong>(alist):
 
bubble_sort_test.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
alist = [0, -1, 0]
 
    @given(lists(integers()))
    def <strong>test_bubble_sort</strong>(alist):
>       assert bubble_sort(alist) == sorted(alist)
E       assert [0, -1, 0] == [-1, 0, 0]
E         At index 0 diff: 0 != -1
E         Full diff:
E         - [-1, 0, 0]
E         + [0, -1, 0]
 
bubble_sort_test.py:11: AssertionError
---------------------------------- Hypothesis ----------------------------------
Falsifying example: test_bubble_sort(
    alist=[0, 0, -1],
)
============================ Hypothesis Statistics =============================
bubble_sort_test.py::test_bubble_sort:
 
  - during reuse phase (0.04 seconds):
    - Typical runtimes: 1-23 ms, ~ 34% in data generation
    - 0 passing examples, 10 failing examples, 0 invalid examples
    - Found 1 failing example in this phase
 
  - during shrink phase (0.04 seconds):
    - Typical runtimes: 0-1 ms, ~ 68% in data generation
    - 14 passing examples, 1 failing examples, 4 invalid examples
    - Tried 19 shrinks of which 0 were successful
 
  - Stopped because nothing left to do
 
 
=========================== short test summary info ============================
FAILED bubble_sort_test.py::test_bubble_sort - assert [0, -1, 0] == [-1, 0, 0]
============================== 1 failed in 0.11s ===============================
Poznámka: algoritmus je poškozen takovým způsobem, že se poprvé projeví právě u seznamu se třemi hodnotami, nikoli u kratších seznamů. Nástroj Hypothesis tedy našel nejmenší množinu vstupních dat – přesně jak jsme očekávali.

10. Specifikace minimální a maximální velikosti vstupů

V testovacích strategiích je možné jednoduše zvolit minimální, popř. maximální velikosti generovaných testovacích dat. To je užitečné například při přípravě seznamů, které mají být seřazeny. Nemusíme totiž začínat testování od seznamu s nulovou délkou, ale instruovat Hypothesis, aby začal vytvářet seznamy o délce 5 či o větší délce. Podívejme se na upravený příklad:

"""Test naivního algoritmu bublinkového řazení."""
 
from hypothesis import given
from hypothesis.strategies import lists, integers
 
from bubble_sort import bubble_sort
 
 
@given(lists(integers(), min_size=5))
def test_bubble_sort(alist):
    assert bubble_sort(alist) == sorted(alist)

Nyní testy (podle očekávání) opět detekují chybu, ale v tomto případě u seznamu s pěti prvky:

$ pytest -v --hypothesis-show-statistics
 
============================= 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/bubble_sort_5/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_5
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
bubble_sort_test.py::test_bubble_sort FAILED                             [100%]
 
=================================== FAILURES ===================================
_______________________________ test_bubble_sort _______________________________
 
    @given(lists(integers(), min_size=5))
>   def test_bubble_sort(alist):
 
bubble_sort_test.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
alist = [0, -1, 0, 0, 0]
 
    @given(lists(integers(), min_size=5))
    def test_bubble_sort(alist):
>       assert bubble_sort(alist) == sorted(alist)
E       assert [0, -1, 0, 0, 0] == [-1, 0, 0, 0, 0]
E         At index 0 diff: 0 != -1
E         Full diff:
E         - [-1, 0, 0, 0, 0]
E         ?            ---
E         + [0, -1, 0, 0, 0]
E         ?  +++
 
bubble_sort_test.py:11: AssertionError
---------------------------------- Hypothesis ----------------------------------
Falsifying example: test_bubble_sort(
    alist=[0, 0, 0, 0, -1],
)
============================ Hypothesis Statistics =============================
bubble_sort_test.py::test_bubble_sort:
 
  - during reuse phase (0.04 seconds):
    - Typical runtimes: 1-23 ms, ~ 37% in data generation
    - 0 passing examples, 10 failing examples, 0 invalid examples
    - Found 1 failing example in this phase
 
  - during shrink phase (0.06 seconds):
    - Typical runtimes: < 1ms, ~ 87% in data generation
    - 5 passing examples, 1 failing examples, 37 invalid examples
    - Tried 43 shrinks of which 0 were successful
 
  - Stopped because nothing left to do
 
 
=========================== short test summary info ============================
FAILED bubble_sort_test.py::test_bubble_sort - assert [0, -1, 0, 0, 0] == [-1...
============================== 1 failed in 0.14s ===============================
Poznámka: z výpisu je patrné, že chyba byla odhalena na pátý pokus, což je velký úspěch. V dalších kapitolách uvidíme, že mnohdy je pro detekci chyby zapotřebí několik stovek a možná i tisíců pokusů (podle toho, jak je chyba zjevná). Navíc se nám oproti předchozímu testu podařilo chybu odhalit dříve.

11. Vylepšení podmínky při neexistenci referenční implementace

Předchozí testy byly založeny na tom, že jsme dokázali najít to nejjednodušší možné orákulum – referenční implementaci algoritmu řazení. To nám umožnilo snadnou kontrolu našeho algoritmu vůči referenční implementaci:

assert bubble_sort(alist) == sorted(alist)

Předpokládejme nyní, že referenční implementace není z nějakého důvodu k dispozici. Testy je tedy nutné přepsat jiným způsobem, a to tak, že se pokusíme nalézt nějakou podmínku, která musí platit pro setříděný seznam. A tato podmínka je zřejmá – každý další prvek seznamu musí být větší nebo roven prvku předchozímu, tedy:

assert all(x<=y for x,y in zip(sorted, sorted[1:]))
Poznámka: použili jsme zde trik s posunem seznamu o jeden prvek tím, že se jeho první prvek odstraní.

Upravený zdrojový text testů:

"""Test naivního algoritmu bublinkového řazení."""
 
from hypothesis import given
from hypothesis.strategies import lists, integers
 
from bubble_sort import bubble_sort
 
 
@given(lists(integers(), min_size=5))
def test_bubble_sort(alist):
    sorted = bubble_sort(alist)
    assert all(x<=y for x,y in zip(sorted, sorted[1:]))

Výsledek běhu takto upravených testů:

$ pytest -v --hypothesis-show-statistics
 
============================= 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/bubble_sort_6/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/bubble_sort_6
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
bubble_sort_test.py::test_bubble_sort FAILED                             [100%]
 
=================================== FAILURES ===================================
_______________________________ test_bubble_sort _______________________________
 
    @given(lists(integers(), min_size=5))
>   def test_bubble_sort(alist):
 
bubble_sort_test.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
alist = [0, -1, 0, 0, 0]
 
    @given(lists(integers(), min_size=5))
    def test_bubble_sort(alist):
        sorted = bubble_sort(alist)
>       assert all(x<=y for x,y in zip(sorted, sorted[1:]))
E       assert False
E        +  where False = all(<generator object test_bubble_sort.<locals>.<genexpr> at 0x7f45ddda0fc0>)
 
bubble_sort_test.py:12: AssertionError
---------------------------------- Hypothesis ----------------------------------
Falsifying example: test_bubble_sort(
    alist=[0, 0, 0, 0, -1],
)
============================ Hypothesis Statistics =============================
bubble_sort_test.py::test_bubble_sort:
 
  - during generate phase (0.06 seconds):
    - Typical runtimes: 0-22 ms, ~ 71% in data generation
    - 12 passing examples, 6 failing examples, 0 invalid examples
    - Found 1 failing example in this phase
 
  - during shrink phase (0.13 seconds):
    - Typical runtimes: 0-1 ms, ~ 83% in data generation
    - 6 passing examples, 14 failing examples, 59 invalid examples
    - Tried 79 shrinks of which 18 were successful
 
  - Stopped because nothing left to do
 
 
=========================== short test summary info ============================
FAILED bubble_sort_test.py::test_bubble_sort - assert False
============================== 1 failed in 0.22s ===============================

12. Jednoduchá šifra typu ROT13

V navazujících kapitolách budeme testovat jiný algoritmus, opět velmi jednoduchý. Bude se jednat o jednu z možných implementací šifry ROT13, v níž jsou písmena z ASCII posunuta o 13 pozic nahoru (s tím, že se používá „modulo aritmetika“). V Pythonu je implementace této šifry snadná a je založena na funkci maketrans:

"""Jednoduchá šifra typu ROT13."""
 
import string
 
 
def rot13(text):
    """Jednoduchá šifra typu ROT13."""
    r = str.maketrans(
        "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",
        "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
    return text.translate(r)
 
 
if __name__ == '__main__':
    sentence = "Příliš žluťoučký kůň úpěl ďábelské ódy."
    print(rot13(sentence))
    print(rot13(rot13(sentence)))

Ve výpisu si můžeme ověřit, že dvojím zašifrováním získáme původní vstup a taktéž to, že znaky mimo základní ASCII nejsou změněny:

Cříyvš žyhťbhčxý xůň úcěy ďáoryfxé óql.
Příliš žluťoučký kůň úpěl ďábelské ódy.
Poznámka: viz též vestavěnou nápovědu:
Help on built-in function maketrans:
 
maketrans(x, y=None, z=None, /)
    Return a translation table usable for str.translate().
  
    If there is only one argument, it must be a dictionary mapping Unicode
    ordinals (integers) or characters to Unicode ordinals, strings or None.
    Character keys will be then converted to ordinals.
    If there are two arguments, they must be strings of equal length, and
    in the resulting dictionary, each character in x will be mapped to the
    character at the same position in y. If there is a third argument, it
    must be a string, whose characters will be mapped to None in the result.

13. Otestování jednoduché šifry

Pro otestování této šifry můžeme použít základní vlastnost šifry ROT13 – dvojí aplikace této šifry by měla vrátit původní text (to je důvod, proč se používá posun o 13 pozic, protože v ASCII je jen 26 písmen ve dvou velikostech):

"""Test jednoduché šifry typu ROT13."""
 
from hypothesis import given
from hypothesis.strategies import text
 
from rot13 import rot13
 
 
@given(text())
def test_rot13(s):
    """Test jednoduché šifry typu ROT13."""
    assert rot13(rot13(s)) == s
Poznámka: to ovšem znamená, že nebudeme schopni otestovat, zda algoritmus není implementován pouze pomocí return původní_řetězec :-)

Test by měl projít bez chyby:

$ pytest -v --hypothesis-show-statistics
 
============================= 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/rot13_1/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/rot13_1
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
rot13_test.py::test_rot13 PASSED                                         [100%]
============================ Hypothesis Statistics =============================
 
rot13_test.py::test_rot13:
 
  - during generate phase (0.16 seconds):
    - Typical runtimes: 0-1 ms, ~ 71% in data generation
    - 100 passing examples, 0 failing examples, 6 invalid examples
 
  - Stopped because settings.max_examples=100
 
 
============================== 1 passed in 0.61s ===============================
Poznámka: povšimněte si, že test byl spuštěn celkem stokrát, pokaždé pochopitelně s náhodně vygenerovaným vstupem.

14. Zanesení chyby do algoritmu šifrování

Do algoritmu šifry nyní zaneseme chybu, která se bude týkat mapování C na P. Namísto toho bude znak C mapován na mezeru:

"""Jednoduchá šifra typu ROT13."""
 
from hypothesis import given
from hypothesis.strategies import text
import string
 
 
def rot13(text):
    """Jednoduchá šifra typu ROT13."""
    r = str.maketrans(
        "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",
        "NO QRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
    return text.translate(r)
 
 
if __name__ == '__main__':
    sentence = "Příliš žluťoučký kůň úpěl ďábelské ódy."
    print(rot13(sentence))
    print(rot13(rot13(sentence)))

Tato chyba je ihned patrná při spuštění hlavního bloku (první písmeno dešifrované věty):

Cříyvš žyhťbhčxý xůň úcěy ďáoryfxé óql.
 říliš žluťoučký kůň úpěl ďábelské ódy.

Zajímavé ovšem je, že po spuštění testů se chyba nenalezne:

$ pytest -v --hypothesis-show-statistics
 
============================= 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/rot13_2/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/rot13_2
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
rot13_test.py::test_rot13 PASSED                                         [100%]
============================ Hypothesis Statistics =============================
 
rot13_test.py::test_rot13:
 
  - during reuse phase (0.00 seconds):
    - Typical runtimes: < 1ms, ~ 72% in data generation
    - 1 passing examples, 0 failing examples, 0 invalid examples
 
  - during generate phase (1.41 seconds):
    - Typical runtimes: 0-2 ms, ~ 85% in data generation
    - 499 passing examples, 0 failing examples, 30 invalid examples
 
  - Stopped because settings.max_examples=500
 
 
============================== 1 passed in 1.45s ===============================

15. Přesnější specifikace podoby generovaných textových dat

Důvodem, proč nebyla chyba nalezena, je fakt, že se Hypothesis snaží generovat co „nejpodivnější“ řetězce, ve kterých se používají různé znaky z celého Unicode. Z tohoto pohledu je znak C či P zcela normální a je jen velmi malá pravděpodobnost, že bude použit a chyba tak bude nalezena. Ovšem snadno můžeme zařídit, aby se vytvářely testovací řetězce z pouze omezené množiny znaků, zde konkrétně znaky s kódy 32 až 127:

"""Test jednoduché šifry typu ROT13."""
 
from hypothesis import given, note, settings
from hypothesis.strategies import text, characters
 
from rot13 import rot13
 
 
@given(text(characters(min_codepoint=32, max_codepoint=127), min_size=5))
@settings(max_examples=500)
def test_rot13(s):
    """Test jednoduché šifry typu ROT13."""
    assert rot13(rot13(s)) == s

Nyní již bude chyba v algoritmu nalezena, a to dokonce velmi rychle – v řádu desítek iterací (a nikoli tisícovek):

$ pytest -v --hypothesis-show-statistics
 
============================= 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/rot13_3/.hypothesis/examples')
rootdir: /home/ptisnovs/src/python/testing-in-python/hypothesis/rot13_3
plugins: print-0.1.3, voluptuous-1.0.2, hypothesis-5.16.0, cov-2.5.1
collecting ... collected 1 item
 
rot13_test.py::test_rot13 FAILED                                         [100%]
 
=================================== FAILURES ===================================
__________________________________ test_rot13 __________________________________
 
    @given(text(characters(min_codepoint=32, max_codepoint=127), min_size=5))
>   @settings(max_examples=500)
    def test_rot13(s):
 
rot13_test.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
s = '0000P'
 
    @given(text(characters(min_codepoint=32, max_codepoint=127), min_size=5))
    @settings(max_examples=500)
    def test_rot13(s):
        """Test jednoduché šifry typu ROT13."""
>       assert rot13(rot13(s)) == s
E       AssertionError: assert '0000 ' == '0000P'
E         - 0000P
E         ?     ^
E         + 0000
E         ?     ^
 
rot13_test.py:13: AssertionError
---------------------------------- Hypothesis ----------------------------------
Falsifying example: test_rot13(
    s='0000P',
)
============================ Hypothesis Statistics =============================
rot13_test.py::test_rot13:
 
  - during generate phase (0.04 seconds):
    - Typical runtimes: 0-22 ms, ~ 65% in data generation
    - 8 passing examples, 2 failing examples, 0 invalid examples
    - Found 1 failing example in this phase
 
  - during shrink phase (0.13 seconds):
    - Typical runtimes: 0-1 ms, ~ 77% in data generation
    - 11 passing examples, 18 failing examples, 48 invalid examples
    - Tried 77 shrinks of which 17 were successful
 
  - Stopped because nothing left to do
 
 
=========================== short test summary info ============================
FAILED rot13_test.py::test_rot13 - AssertionError: assert '0000 ' == '0000P'
============================== 1 failed in 0.60s ===============================

16. Metody map a filter

Ve strategiích je možné použít i metody filter a map pro omezení množiny vstupních dat. Díky tomu lze například zařídit – i když poměrně neefektivně – aby se vytvářely seznamy s kladnými sudými čísly:

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í):

[]
[]
[]
[114, 297635958, 36382594596201776729035817479773596128]
[]
[]
[1584, 20756, 27064]
[]
[]
[6256, 16432]

Podobně lze omezit počet prvků v generovaných seznamech (což je ovšem velmi neefektivní způsob):

from hypothesis.strategies import lists, integers
 
g = lists(integers().filter(lambda x: x > 0 and x % 2 == 0)).filter(lambda x: len(x) > 2 and len(x) < 6)
 
for _ in range(10):
    print(g.example())

Opět si uveďme příklad výsledku (bude se lišit v každém spuštění):

[692, 25368, 17078, 8076]
[32726, 187067303079521214, 30482]
[32114, 31920, 120, 34, 4414]
[26344, 49216631145512026322426062367560067482, 98, 32088, 2480]
[8892, 14870, 2046]
[110, 9412, 26768]
[10434, 58, 7836, 12460]
[141471345277595789266895116305060658262, 8302, 8470]
[921905719452052336, 5382, 40]
[7652, 638723812, 2104125796, 19320]

Funkce map dokáže zajistit, že na vstupu bude test dostávat setříděné seznamy:

from hypothesis.strategies import lists, integers
 
g = lists(integers()).map(sorted)
 
for _ in range(10):
    print(g.example())

S výsledky:

[0]
[-20957]
[0]
[0]
[0]
[0]
[]
[0]
[]
[0]

A nakonec si všechny předchozí příklady můžeme zkombinovat – bude se generovat seznam se sudými kladnými čísly, jejichž počet se bude pohybovat od tří do pěti:

from hypothesis.strategies import lists, integers
 
g = lists(integers().filter(lambda x: x > 0 and x % 2 == 0)).filter(lambda x: len(x) > 2 and len(x) < 6).map(sorted)
 
for _ in range(10):
    print(g.example())

S výsledky:

[12, 6276, 20218, 639217126, 1354229336]
[6, 52, 82, 165210043927242131602779831719279821612]
[1634, 11134, 22630, 2119407794]
[26, 2586, 14584, 28060]
[62, 2606, 8938]
[92, 10582, 886236136, 1731330112]
[28, 46, 4008526909638794906]
[4, 42, 112]
[96, 108, 25038, 25970]
[22, 126, 13974]
Poznámka: praktické použití si ukážeme příště.

17. Kombinace více strategií

Strategie lze taktéž zkombinovat, a to s využitím přetíženého operátoru |. Příkladem může být generování hodnot, které mohou být typu None (tedy jediná hodnota None), typu Boolean (True či False), popř. celé číslo v rozsahu 0 až 1000:

from hypothesis.strategies import none, booleans, integers
 
g = none() | booleans() | integers(min_value=0, max_value=1000)
 
for _ in range(20):
    print(g.example())

Příklad výsledku:

bitcoin_skoleni

True
244
None
339
42
False
False
None
False
341
264
956
None
0
934
None
True
999
False
522
Poznámka: praktické použití tohoto velmi užitečného mechanismu si opět ukážeme příště.

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:

# Příklad Stručný popis Cesta
1 bubble_sort1/bubble_sort.py implementace algoritmu bublinkového řazení https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort1/bubble_sor­t.py
2 bubble_sort1/bubble_sort_test.py klasické jednotkové testy pro algoritmu bubble sort https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort1/bubble_sor­t_test.py
       
3 bubble_sort2/bubble_sort.py implementace algoritmu bublinkového řazení https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort2/bubble_sor­t.py
4 bubble_sort2/bubble_sort_test.py jednotkové testy používající Hypothesis https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort2/bubble_sor­t_test.py
       
5 bubble_sort3/bubble_sort.py implementace algoritmu bublinkového řazení https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort3/bubble_sor­t.py
6 bubble_sort3/bubble_sort_test.py upravené jednotkové testy používající Hypothesis https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort3/bubble_sor­t_test.py
       
7 bubble_sort4/bubble_sort.py implementace poškozeného algoritmu bublinkového řazení https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort4/bubble_sor­t.py
8 bubble_sort4/bubble_sort_test.py jednotkové testy používající Hypothesis https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort4/bubble_sor­t_test.py
       
9 bubble_sort5/bubble_sort.py implementace poškozeného algoritmu bublinkového řazení https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort5/bubble_sor­t.py
10 bubble_sort5/bubble_sort_test.py upravené jednotkové testy používající Hypothesis https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort5/bubble_sor­t_test.py
       
11 bubble_sort6/bubble_sort.py implementace poškozeného algoritmu bublinkového řazení https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort6/bubble_sor­t.py
12 bubble_sort6/bubble_sort_test.py upravené jednotkové testy, které nejsou založeny na referenční implementaci https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/bubble_sort6/bubble_sor­t_test.py
       
13 rot13_1/rot13.py implementace šifry ROT-13 https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/rot13_1/rot13.py
14 rot13_1/rot13_test.py jednotkové testy používající Hypothesis https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/rot13_1/rot13_test.py
       
15 rot13_2/rot13.py implementace chybného algoritmu šifry ROT-13 https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/rot13_2/rot13.py
16 rot13_2/rot13_test.py jednotkové testy používající Hypothesis https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/rot13_2/rot13_test.py
       
17 rot13_3/rot13.py implementace chybného algoritmu šifry ROT-13 https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/rot13_3/rot13.py
18 rot13_3/rot13_test.py jednotkové testy používající Hypothesis se změnou sady znaků v generovaných řetězcích https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/rot13_3/rot13_test.py
       
19 list_gen.py strategie pro generování seznamu celých čísel https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/list_gen.py
20 text_gen1.py strategie pro generování textů (řetězců) https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/text_gen1.py
21 text_gen2.py strategie pro generování textů (řetězců) https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/text_gen2.py
22 filter1.py strategie s filtrováním https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/filter1.py
23 filter2.py strategie s filtrováním https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/filter2.py
24 map1.py strategie s mapováním na jiné hodnoty https://github.com/tisnik/testing-in-python/tree/master/hypothesis/map1.py
25 map2.py strategie s mapováním na jiné hodnoty https://github.com/tisnik/testing-in-python/tree/master/hypothesis/map2.py
26 combination_gen.py kombinace tří strategií https://github.com/tisnik/testing-in-python/tree/master/hypothe­sis/combination_gen.py

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:

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

Autor článku

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