Hlavní navigace

Struktura projektů s jednotkovými testy, využití Travis CI

 Autor: Depositphotos
Dnes si ukážeme, jak může vypadat struktura projektů, v nichž se (pochopitelně kromě vlastního programového kódu) používají i jednotkové testy, popř. i testy integrační. Nezapomeneme ale ani na nastavení CI.
Pavel Tišnovský 28. 5. 2020
Doba čtení: 29 minut

Sdílet

11. Nastavení CI – služby zajišťující průběžné integrace

12. Vytvoření projektu s jeho registrací v Travis CI

13. Konfigurační soubor pro službu Travis CI

14. Automatické otestování změny

15. Oprava chyb nalezených úlohami na Travis CI

16. Prohlížení výsledků

17. Další možnosti nabízené Travis CI

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. Struktura projektů s jednotkovými testy

V první části dnešního článku o testování s využitím programovacího jazyka Python si řekneme, jak by mohl vypadat jednoduchý projekt naprogramovaný v Pythonu a doplněný o jednotkové testy. Struktura takového projektu, tj. (velmi zjednodušeně řečeno) rozmístění souborů se zdrojovými kódy i s testy, může nabývat několika podob; většinou se však setkáme se čtyřmi variantami, o nichž se zmíníme v navazujících kapitolách. Každá z těchto variant má své přednosti, ale i zápory a z tohoto důvodu se ve světě Pythonu setkáme s různě strukturovanými projekty. Dále si popíšeme jednoduchý soubor Makefile, který sice – alespoň na první pohled – není pro projekty psané v interpretovaném jazyce nezbytný, ale může se jednat o užitečnou náhradu za sadu jednorázových skriptů (jež jsme ostatně viděli v projektech popisovaných v předchozích dvou částech tohoto seriálu).

Poznámka: ve skutečnosti existuje ještě více různých variant, protože zejména v časech Pythonu 2 byla doporučení značně nejednotná.

2. Jednotkové testy umístěné v samostatném podadresáři

S prvním způsobem strukturování projektu vytvořeného v Pythonu se možná čtenáři tohoto článku již několikrát setkali. Samotný projekt je v tomto případě uložen v podadresáři či podadresářích, jejichž jména odpovídají jménům balíčků. V našem konkrétním případě se jedná o balíček nazvaný average, tj. jeho zdrojové soubory jsou umístěny v podadresáři average. Vzhledem k tomu, že se jedná skutečně o plnohodnotný balíček, nesmíme zapomenout ani na soubor nazvaný __init__.py, bez jehož existence by spousta věcí „náhodně“ přestala fungovat. Jednotkové testy tvoří taktéž vlastní balíček a jsou umístěny v podadresáři tests. Kromě toho je v projektu uložen i soubor main.py, který ukazuje, jak se dá naimportovat a použít balíček average a zapomenout nesmíme ani na soubor Makefile, o němž se blíže zmíníme dále:

├── average
│   ├── average.py
│   └── __init__.py
│
├── tests
│   ├── __init__.py
│   ├── test_advanced.py
│   └── test_basic.py
│
├── main.py
├── Makefile
├── requirements.txt
└── setup.cfg

Předností této struktury je především možnost spouštět jednotkové testy pouze zadáním příkazu pytest. Dále je možné testy spouštět i bez nutnosti instalace testovaného balíčku (což jiné struktury projektů buď vůbec neumožňují, nebo se jedná o komplikovanější záležitost).

3. Obsah jednotlivých souborů tvořících celý projekt

Jen ve stručnosti (v dalších příkladech se již těmito podrobnostmi nebudeme zabývat) si ukažme obsah jednotlivých souborů, které jsou v projektu umístěny.

Soubor average/average.py s vlastní implementací funkce, kterou budeme testovat:

"""Výpočet průměru."""
 
 
def avg(x):
    """Výpočet průměru ze seznamu hodnot předaných v parametru x."""
    return sum(x)/float(len(x))

Zapomenout nesmíme ani na důležitý soubor average/__init__.py, který vlastně z pouhého adresáře vytváří plnohodnotný Pythonovský balíček:

"""Balíček obsahující funkci pro výpočet průměru."""
 
from .average import avg
Poznámka: povšimněte si, jakým způsobem je zajištěno zveřejnění testované funkce.

Soubor tests/test_basic.py obsahující základní jednotkové testy:

"""Implementace jednotkových testů."""
 
import pytest
 
import average
 
 
def test_average_basic():
    """Otestování výpočtu průměru."""
    result = average.avg([1, 2])
    expected = 1.5
    assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
 
 
def test_average_empty_list():
    """Otestování výpočtu průměru pro prázdný vstup."""
    with pytest.raises(ZeroDivisionError) as excinfo:
        result = average.avg([])

Soubor tests/test_advanced.py obsahující další jednotkové testy:

"""Implementace jednotkových testů."""
 
import pytest
 
import average
 
 
@pytest.mark.parametrize(
    "values,expected",
    (
        pytest.param(
            (1, 1), 1
        ),
        pytest.param(
            (1, 2), 1.5
        ),
        pytest.param(
            (0, 1), 0.5
        ),
        pytest.param(
            (1, 2, 3), 2.0
        ),
        pytest.param(
            (0, 10), 5.0
        ),
    ),
)
def test_average_more_values(values, expected):
    """Otestování výpočtu průměru."""
    result = average.avg(values)
    assert result == expected, "Očekávaná hodnota {}, vráceno {}".format(expected, result)
Poznámka: povšimněte si, že jednotkové testy mohou být umístěny do souborů prakticky libovolně, ovšem je vhodné dodržovat určité jmenné konvence.

A nakonec soubor main.py, ve kterém je ukázáno, jakým způsobem je možné importovat testovaný balíček a spustit funkci, která je v něm deklarována:

#!/usr/bin/env python3
 
"""Vstupní bod do testované aplikace."""
 
from average import avg
 
 
if __name__ == '__main__':
    # pouze se ujistíme, že lze spustit funkci average
    print(avg([1, 2]))
Poznámka: tento soubor má nastaven „shebang“ i příznaky pro spuštění aktuálně přihlášeným uživatelem.

4. Spuštění aplikace i jednotkových testů

Vzhledem k výše zmíněné existenci shebangu i příznaku +ux je možné soubor main.py přímo spustit a nechat si tak vypočítat průměr ze seznamu čísel 1 a 2:

$ ./main.py
 
1.5

Vzhledem k nastavení projektu (a jeho celkové struktuře) je možné spustit i jednotkové testy, a to tím nejjednodušším možným způsobem, tj. bez nutnosti uvádění cest, modifikací PYTHONPATH atd.:

$ pytest
 
============================= test session starts ==============================
platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project1
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collected 7 items
 
tests/test_advanced.py .....                                             [ 71%]
tests/test_basic.py ..                                                   [100%]
 
============================== 7 passed in 0.03s ===============================

Samozřejmě budou funkční i další přepínače:

$ pytest -v tests
 
============================= 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
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project1
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collecting ... collected 7 items
 
tests/test_advanced.py::test_average_more_values[values0-1] PASSED       [ 14%]
tests/test_advanced.py::test_average_more_values[values1-1.5] PASSED     [ 28%]
tests/test_advanced.py::test_average_more_values[values2-0.5] PASSED     [ 42%]
tests/test_advanced.py::test_average_more_values[values3-2.0] PASSED     [ 57%]
tests/test_advanced.py::test_average_more_values[values4-5.0] PASSED     [ 71%]
tests/test_basic.py::test_average_basic PASSED                           [ 85%]
tests/test_basic.py::test_average_empty_list PASSED                      [100%]
 
============================== 7 passed in 0.02s ===============================

5. Soubor Makefile versus sada pomocných skriptů

Ve druhé kapitole jsme si mj. řekli, že součástí projektu je i soubor Makefile. To může být poněkud překvapivé, protože Python (přesněji řečeno CPython) není kompilovaný ani linkovaný programovací jazyk, takže by se mohlo zdát, že technologie poskytovaná soubory Makefile a nástrojem make zde nebude mít velký význam. Ovšem díky Makefile lze mnoho operací, které lze s projektem dělat (spuštění, otestování, zabalení, vytvoření instalačního souboru) zapsat do jediného souboru a při vhodném nastavení bude pouze tento soubor (a žádný jiný) obsahovat konfigurovatelné části resp. části relevantní pouze pro určitý operační systém. První varianta souboru Makefile obsahuje pouze několik pravidel nazvaných init, run, test, coverage, coverage_report a clean:

PYTHON=python3
 
# can be pip, pip2, or pip3
PIP_TOOL=pip
 
TEST_TOOL=pytest
 
init:
        ${PIP_TOOL} install -r requirements.txt
 
run:
        ${PYTHON} main.py
 
test:
        ${TEST_TOOL} -v tests
 
coverage:
        ${TEST_TOOL} --cov=average --cov-report term-missing
 
coverage_report:
        ${TEST_TOOL} --cov=average --cov-report html
 
clean:
        find . -type d -name __pycache__ -exec rm -r {} \+
        rm -rf .pytest_cache/
        rm -rf .benchmarks/
        rm .coverage
 
.PHONY: init test clean
Důležité upozornění: redakční systém expanduje znaky Tab na mezery. Z tohoto důvodu není možné obsah tohoto souboru pouze zkopírovat, ale je nutné použít originální soubor dostupný na adrese https://github.com/tisnik/testing-in-python/blob/master/projec­ts/project1/Makefile.

S využitím Makefile lze například spustit jednotkové testy:

$ make test

S výsledkem:

pytest -v tests
============================= 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
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project1
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collecting ... collected 7 items
 
tests/test_advanced.py::test_average_more_values[values0-1] PASSED       [ 14%]
tests/test_advanced.py::test_average_more_values[values1-1.5] PASSED     [ 28%]
tests/test_advanced.py::test_average_more_values[values2-0.5] PASSED     [ 42%]
tests/test_advanced.py::test_average_more_values[values3-2.0] PASSED     [ 57%]
tests/test_advanced.py::test_average_more_values[values4-5.0] PASSED     [ 71%]
tests/test_basic.py::test_average_basic PASSED                           [ 85%]
tests/test_basic.py::test_average_empty_list PASSED                      [100%]
 
============================== 7 passed in 0.02s ===============================

Dále je možné zjistit pokrytí kódu jednotkovými testy:

$ make coverage

S výsledkem:

pytest --cov=average --cov-report term-missing
============================= test session starts ==============================
platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project1
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collected 7 items
 
tests/test_advanced.py .....                                             [ 71%]
tests/test_basic.py ..                                                   [100%]
 
----------- coverage: platform linux, python 3.6.6-final-0 -----------
Name                  Stmts   Miss  Cover   Missing
---------------------------------------------------
average/__init__.py       1      0   100%
average/average.py        2      0   100%
---------------------------------------------------
TOTAL                     3      0   100%
 
 
============================== 7 passed in 0.05s ===============================

A nakonec „uklidit“ všechny soubory, které v rámci testování vznikly:

$ make clean

6. Druhý způsob: zdrojové kódy v podadresáři src

U některých projektů se setkáme s poněkud odlišnou strukturou adresářů a podadresářů, konkrétně s tím, že balíčky obsahující implementaci aplikace (či jiného vyvíjeného nástroje) jsou uloženy v podadresáři nazvaném src. Celá hierarchie souborů je tak o jeden adresář hlubší, jak je ostatně patrné ze zobrazené stromové struktury:

├── src
│   └── average
│       ├── average.py
│       └── __init__.py
│
├── tests
│   ├── __init__.py
│   ├── test_advanced.py
│   └── test_basic.py
│
├── main.py
├── Makefile
├── requirements.txt
└── setup.cfg
Poznámka: tuto strukturu mnohdy vytváří vývojáři, kteří na Python přešli z jiných programovacích jazyků, kde je naopak takové rozmístění souborů velmi často používáno.

Pokud ovšem neprovedeme další změny v nastavení projektu, nebude mnoho operací možné provést. Například se nepovede již spuštění aplikace:

$ ./main.py
 
Traceback (most recent call last):
  File "./main.py", line 5, in <module>
    from average import avg
ModuleNotFoundError: No module named 'average'

Podobný problém budou hlásit i jednotkové testy při snaze o jejich spuštění:

$ make test

S výsledky:

pytest -v tests
============================= 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
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project2
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collecting ... collected 0 items / 2 errors
 
==================================== ERRORS ====================================
___________________ ERROR collecting tests/test_advanced.py ____________________
ImportError while importing test module '/home/ptisnovs/src/python/testing-in-python/projects/project2/tests/test_advanced.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_advanced.py:5: in <module>
    import average
E   ModuleNotFoundError: No module named 'average'
_____________________ ERROR collecting tests/test_basic.py _____________________
ImportError while importing test module '/home/ptisnovs/src/python/testing-in-python/projects/project2/tests/test_basic.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_basic.py:5: in <module>
    import average
E   ModuleNotFoundError: No module named 'average'
=========================== short test summary info ============================
ERROR tests/test_advanced.py
ERROR tests/test_basic.py
!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!!
============================== 2 errors in 0.10s ===============================

7. Oprava předchozí struktury s využitím souboru conftest.py

Oprava problémů s cestami k balíčkům v podadresáři src může být v případě skriptu main.py řešena následujícím způsobem:

#!/usr/bin/env python3
 
"""Vstupní bod do testované aplikace."""
 
from src.average import avg
 
 
if __name__ == '__main__':
    # pouze se ujistíme, že lze spustit funkci average
    print(avg([1, 2]))
Poznámka: v žádném případě se nejedná o ideální způsob; existuje více možností, jak import provést s tím, že se vynechá „nadbytečné“ jméno adresáře src.

Oprava testů, resp. umožnění jejich spuštění, je ve skutečnosti jednodušší, než by se mohlo zdát. Do adresáře src totiž postačuje přidat prázdný soubor nazvaný conftest.py, takže se celková struktura projektu změní následujícím způsobem:

├── src
│   ├── average
│   │   ├── average.py
│   │   └── __init__.py
│   └── conftest.py
│
├── tests
│   ├── __init__.py
│   ├── test_advanced.py
│   └── test_basic.py
│
├── main.py
├── Makefile
├── requirements.txt
└── setup.cfg

Po této nepatrné úpravě již bude možné jednotkové testy spustit:

$ make test

S výsledky:

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
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project3
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collecting ... collected 7 items
 
tests/test_advanced.py::test_average_more_values[values0-1] PASSED       [ 14%]
tests/test_advanced.py::test_average_more_values[values1-1.5] PASSED     [ 28%]
tests/test_advanced.py::test_average_more_values[values2-0.5] PASSED     [ 42%]
tests/test_advanced.py::test_average_more_values[values3-2.0] PASSED     [ 57%]
tests/test_advanced.py::test_average_more_values[values4-5.0] PASSED     [ 71%]
tests/test_basic.py::test_average_basic PASSED                           [ 85%]
tests/test_basic.py::test_average_empty_list PASSED                      [100%]
 
============================== 7 passed in 0.03s ===============================
Poznámka: soubor conftest.py má několik úkolů. Slouží například k definici test fixtures, načítání externích modulů do kontextu, v němž se jednotkové testy spouští, dále je v něm možné definovat funkce typu pytest_runtest_setup a nakonec tyto soubory určují takzvaný root path, a to bez nutnosti změny proměnné prostředí PYTHONPATH. Právě poslední způsob byl použitý v upraveném demonstračním příkladu.

8. Třetí způsob: jednotkové testy ve svém podadresáři

I s třetím způsobem se můžeme někdy setkat, zejména u rozsáhlejších projektů, které kromě jednotkových testů obsahují například i integrační testy, testy REST API atd. Upravená struktura ukládá jednotkové testy nikoli přímo do podadresáře tests, ale o jednu úroveň níže, konkrétně do podadresáře tests/unit_tests:

├── src
│   ├── average
│   │   ├── average.py
│   │   └── __init__.py
│   └── conftest.py
│
├── tests
│   └── unit_tests
│       ├── __init__.py
│       ├── test_advanced.py
│       └── test_basic.py
│
├── main.py
├── Makefile
├── requirements.txt
└── setup.cfg

V tomto případě je stále možné jednotkové testy spouštět pouze příkazem pytest, o čemž se ostatně můžeme velmi snadno přesvědčit:

$ pytest
 
============================= test session starts ==============================
platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project4
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collected 7 items
 
tests/unit_tests/test_advanced.py .....                                  [ 71%]
tests/unit_tests/test_basic.py ..                                        [100%]
 
============================== 7 passed in 0.03s ===============================
Poznámka: ve skutečnosti není výše uvedený příkaz zcela korektní, protože pokud se budou v podadresáři tests nacházet například i integrační testy, byly by tyto spuštěny taktéž. To, které funkce a metody s testy byly nalezeny a mají se spustit, zjistíme relativně snadno:
$ pytest --collect-only
 
============================= test session starts ==============================
platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project4
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collected 7 items
<Package /home/ptisnovs/src/python/testing-in-python/projects/project4/tests/unit_tests>
  <Module test_advanced.py>
    <Function test_average_more_values[values0-1]>
    <Function test_average_more_values[values1-1.5]>
    <Function test_average_more_values[values2-0.5]>
    <Function test_average_more_values[values3-2.0]>
    <Function test_average_more_values[values4-5.0]>
  <Module test_basic.py>
    <Function test_average_basic>
    <Function test_average_empty_list>
 
============================ no tests ran in 0.02s =============================

Výběr adresáře s testy lze provést například takto (viz též předchozí část tohoto seriálu):

$ pytest -k tests/unit_tests
 
============================= test session starts ==============================
platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project4
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collected 12 items / 5 deselected / 7 selected
 
tests/unit_tests/test_advanced.py .....                                  [ 71%]
tests/unit_tests/test_basic.py ..                                        [100%]
 
======================= 7 passed, 5 deselected in 0.04s ========================

9. Čtvrtý způsob: umístění jednotkových testů do podadresáře s testovaným modulem

I se čtvrtou strukturou projektu se můžeme setkat, a to poměrně často. V této struktuře jsou jednotkové testy umístěny v rámci jednotlivých balíčků, v našem případě uvnitř balíčku average v podadresáři tests:

├── average
│   ├── __init__.py
│   ├── average.py
│   │
│   └── tests
│       ├── __init__.py
│       ├── test_advanced.py
│       └── test_basic.py
│
├── main.py
├── Makefile
├── requirements.txt
└── setup.cfg

Spuštění aplikace se provede standardním způsobem a i jednotkové testy lze spustit bez nutnosti jejich úprav, což si ostatně ihned otestujeme:

$ pytest
 
============================= test session starts ==============================
platform linux -- Python 3.6.6, pytest-5.4.2, py-1.5.2, pluggy-0.13.1
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/ptisnovs/src/python/testing-in-python/projects/project5
plugins: print-0.1.3, voluptuous-1.0.2, benchmark-3.2.3, csv-2.0.2, cov-2.5.1
collected 7 items
 
average/tests/test_advanced.py .....                                     [ 71%]
average/tests/test_basic.py ..                                           [100%]
 
============================== 7 passed in 0.04s ===============================
Poznámka: tato struktura projektu může být výhodná tehdy, pokud je projekt složen z většího množství různých balíčků. V takovém případě nemusí být jednotkové testy umístěny a promíchány v jediném podadresáři tests (s případnou kopií podadresářové struktury).

10. Užitečný (špinavý) trik: nápověda v Makefile

Soubory Makefile mohou být poměrně nečitelné, zejména ve chvíli, kdy se v nich začne objevovat větší množství pravidel. Ovšem můžeme využití jednoho triku a vygenerovat přímo z obsahu Makefile nápovědu. Ta je založena na tom, že se za jméno každého pravidla napíše komentář, který dané pravidlo (což je z hlediska uživatele příkaz zadávaný na příkazovou řádku) popisuje, a tento komentář je vhodným způsobem zpracován v pravidle nazvaném help. Jedno z možných řešení spočívá ve využití nástroje grep kombinovaného s awk, ovšem existují i další možné způsoby:

PYTHON=python3
 
# can be pip, pip2, or pip3
PIP_TOOL=pip
 
TEST_TOOL=pytest
 
help: ## Show this help screen
        @echo 'Usage: make <OPTIONS> ... <TARGETS>'
        @echo ''
        @echo 'Available targets are:'
        @echo ''
        @grep -E '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
                awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
        @echo ''
 
init: ## Install this project using pip tool
        ${PIP_TOOL} install -r requirements.txt
 
run: ## Run project
        ${PYTHON} main.py
 
test: ## Test the project (run unit tests)
        ${TEST_TOOL} -v --pyargs average
 
coverage: ## Display test code coverage
        ${TEST_TOOL} --cov=average --cov-report term-missing
 
coverage_report: ## Generate code coverage report
        ${TEST_TOOL} --cov=average --cov-report html
 
clean: ## Cleanup repository
        find . -type d -name __pycache__ -exec rm -r {} \+
        rm -rf .pytest_cache/
        rm -rf .benchmarks/
        rm .coverage
 
.PHONY: init test clean help

Pravidlo help je uvedeno na prvním místě, takže je spouštěno i ve chvíli, kdy se zadá pouze příkaz:

$ make

S následujícím výsledkem:

Usage: make <OPTIONS> ... <TARGETS>
 
Available targets are:
 
help                 Show this help screen
init                 Install this project using pip tool
run                  Run project
test                 Test the project (run unit tests)
coverage             Display test code coverage
coverage_report      Generate code coverage report
clean                Cleanup repository
Poznámka: podobným způsobem je možné relativně snadno přidat a současně i zdokumentovat další pravidla.

11. Nastavení CI – služby zajišťující průběžné integrace

Ve druhé části dnešního článku si ukážeme jedno z možných nastavení CI neboli Continuous Integration. Jedná se o sadu nástrojů a služeb, které mj. slouží k vyhledávání problematických či dokonce chybných míst ve zdrojovém kódu. Typicky se v rámci CI spouští větší množství analytických nástrojů a kromě jiného zde mají prakticky nezastupitelnou úlohu i jednotkové testy, jimiž jsme se již v tomto seriálu intenzivně zabývali. Dnes si ukážeme, jakým způsobem je možné použít službu nazvanou přímočaře Travis CI, jejíž volně použitelná verze je dostupná na adrese https://travis-ci.org/. Samozřejmě se ani zdaleka nejedná o jedinou službu pro CI, ovšem předností Travis CI je jednoduchá konfigurace (pomocí jediného souboru ve formátu YAML nebo JSON) a dobrá integrace s GitHubem.

Obrázek 1: Dashboard služby Travis CI.

12. Vytvoření projektu s jeho registrací v Travis CI

Nejdříve si vytvoříme repositář s novým projektem, na kterém si nastavení s Travis CI ukážeme. Tento repositář je umístěn na adrese https://github.com/tisnik/python-project a celý projekt má následující strukturu:

├── average
│   ├── average.py
│   └── __init__.py
├── main.py
├── Makefile
├── README.md
├── requirements.txt
├── setup.cfg
└── tests
    ├── __init__.py
    ├── test_advanced.py
    └── test_basic.py

Jedná se tedy o strukturu, o níž jsme se podrobněji zmiňovali ve druhé kapitole. Za zmínku stojí soubor Makefile, jehož nový obsah je následující:

PYTHON=python3
 
# can be pip, pip2, or pip3
PIP_TOOL=pip
 
PYCODESTYLE_TOOL=pycodestyle
 
TEST_TOOL=pytest
 
init:
        ${PIP_TOOL} install -r requirements.txt
 
run:
        ${PYTHON} main.py
 
test:
        ${TEST_TOOL} -v tests
 
style:
        ${PYCODESTYLE_TOOL}
 
coverage:
        ${TEST_TOOL} --cov=average --cov-report term-missing
 
coverage_report:
        ${TEST_TOOL} --cov=average --cov-report html
 
clean:
        find . -type d -name __pycache__ -exec rm -r {} \+
        rm -rf .pytest_cache/
        rm -rf .benchmarks/
        rm .coverage
 
.PHONY: init test clean
Poznámka: povšimněte si především nového pravidla style.

Dále je nutné se zaregistrovat na stránce https://travis-ci.org/ a po (doufejme, že úspěšné) registraci přidat nový projekt do dashboardu.

Obrázek 2: Stav projektu, pro který sice existuje repositář, ovšem nebyl (prozatím) zaregistrován v Travis CI.

Obrázek 3: Aktivace nového projektu. V první fázi ještě nebyly spuštěny žádné úlohy, takže je se pouze zobrazí tato informační obrazovka.

Poznámka: projekt by měl být veřejně viditelný, tedy s nastavením public.

13. Konfigurační soubor pro službu Travis CI

V repositáři musí být umístěn i konfigurační soubor pro službu Travis CI. Název tohoto souboru je .travis.yml a jeho minimální podoba vypadá (pro projekt napsaný v Pythonu) následovně:

language: python
script:
  - pytest

Takto vytvořený konfigurační soubor zajistí, že se pro každou změnu (realizovanou přes „pull request“) spustí nám již dobře známý nástroj pytest a na základě jeho návratového kódu se rozhodne, zda testy skončily úspěšně či nikoli.

Ukažme si ještě složitější podobu konfiguračního souboru, v němž je deklarováno několik úloh (jobs). Ty jsou prováděny samostatně a postupně; pokud nějaká úloha skončí s chybou, další úlohy se již nespouští. První úloha se jmenuje style a spustí kontrolu stylu zápisu zdrojových kódů. Druhá úloha má název unit tests a spouští (nepřekvapivě) jednotkové testy. A konečně třetí úloha prozatím pouze vypisuje text „DEPLOY“ (a vždy tedy skončí s úspěchem). A konečně máme deklarován umělý test, který pouze vypíše verzi Pythonu, a to pro všechny verze zmíněné v sekci python::

language: python
python:
  - "3.4"
  - "3.5"
  - "3.6"
  - "3.7"
  - "3.8"
  - "3.8-dev"  # 3.8 development branch
  - "nightly"  # nightly build
jobs:
  include:
    - stage: style
      script:
        - make style
    - stage: unit tests
      script:
        - make test
    - stage: deploy
      script:
        - echo "DEPLOY"
stages:
  - style
  - unit tests
  - deploy
script:
  - python --version
Poznámka: díky poslední vlastnosti lze na Travis CI otestovat aplikaci pod více verzemi interpretru Pythonu, což může být u některých projektů dosti důležité.

14. Automatické otestování změny

Nyní se již můžeme pokusit vytvořit „pull request“, v němž budou jak problémy ve stylu zápisu zdrojového kódu, tak i chyby (výpočet průměru je schválně napsán špatně). Na stránce s pull requestem by se měla objevit informace o tom, že probíhají testy a následně i výsledky těchto testů:

Obrázek 4: Obě dvě nakonfigurované úlohy skončily s chybou.

Samozřejmě je možné přejít (přes odkaz details) na detailnější informace, což je vlastně strukturovaný a obarvený log s výsledky obou testů:

Obrázek 5: Podrobnější údaje o první úloze, která skončila s chybou.

Obrázek 6: Podrobnější údaje o druhé úloze, která skončila s chybou.

15. Oprava chyb nalezených úlohami na Travis CI

Chyby je možné opravit (v novém commitu), což povede k automatickému znovuspuštění testů na straně služby Travis CI:

Obrázek 7: Spuštění nových testů po opravách.

Nyní (pokud jsme pochopitelně opravili vše) by měly být výsledky zelené:

Obrázek 8: Nově spuštěné testy nyní skončily bez chyby.

16. Prohlížení výsledků

Podívat se můžeme i na podrobnější informace o všech úlohách, které byly na Travis CI spuštěny. Příslušné stránky by měly vypadat následovně:

Obrázek 9: Pohled na podrobnější informace o první úloze.

Obrázek 10: Pohled na podrobnější informace o druhé úloze.

Poznámka: důležité jsou v tomto případě poslední řádky informující o tom, že vše proběhlo v pořádku.

Obrázek 11: Celkový pohled na všechny úlohy a jejich výsledky pro danou změnu.

17. Další možnosti nabízené Travis CI

Služba Travis CI obsahuje i dvě stránky s ucelenými informacemi o všech nedávno spuštěných úlohách i jejích výsledcích, a to pro všechny repositáře, ke kterým má přihlášený uživatel nějaký vztah (majitel kódu, přispěvatel…). To je užitečné zejména ve chvíli, kdy se pracuje s mnoha repositáři a na několika projektech:

Obrázek 12: Sjednocené informace o repositářích, které jsou spravovány v Travis CI.

Obrázek 13: Informace o jednotlivých úlohách.

Obrázek 14: Web UI dokáže zobrazit i konfiguraci, a to jak v původním formátu (YAML), tak i v JSONu.

MIF obecny

Poznámka: ideální je spojit možnosti Travis CI s dalšími službami, které dokážou informovat o pokrytí kódu testy atd. S touto tématikou se seznámíme v navazujícím článku.

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 project1 první způsob: jednotkové testy umístěné v samostatném podadresáři https://github.com/tisnik/testing-in-python/tree/master/projects/project1
2 project2 druhý způsob: zdrojové kódy v podadresáři src https://github.com/tisnik/testing-in-python/tree/master/projects/project2
3 project3 oprava předchozí struktury s využitím souboru conftest.py https://github.com/tisnik/testing-in-python/tree/master/projects/project3
4 project4 třetí způsob: jednotkové testy ve svém podadresáři https://github.com/tisnik/testing-in-python/tree/master/projects/project4
5 project5 čtvrtý způsob: umístění jednotkových testů do podadresáře s testovaným modulem https://github.com/tisnik/testing-in-python/tree/master/projects/project5
6 project6 projekt s upraveným souborem Makefile https://github.com/tisnik/testing-in-python/tree/master/projects/project6

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