Obsah
1. Struktura projektů s jednotkovými testy, využití Travis CI
2. První způsob: jednotkové testy umístěné v samostatném podadresáři
3. Obsah jednotlivých souborů tvořících celý projekt
4. Spuštění aplikace i jednotkových testů
5. Soubor Makefile versus sada pomocných skriptů
6. Druhý způsob: zdrojové kódy v podadresáři src
7. Oprava předchozí struktury s využitím souboru conftest.py
8. Třetí způsob: jednotkové testy ve svém podadresáři
9. Čtvrtý způsob: umístění jednotkových testů do podadresáře s testovaným modulem
10. Užitečný (špinavý) trik: nápověda v Makefile
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
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
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).
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
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)
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]))
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
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
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]))
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 ===============================
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 ===============================
$ 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 ===============================
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
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
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.
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
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.
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.
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:
- Použití Pythonu pro tvorbu testů: od jednotkových testů až po testy UI
https://www.root.cz/clanky/pouziti-pythonu-pro-tvorbu-testu-od-jednotkovych-testu-az-po-testy-ui/ - Použití Pythonu pro tvorbu testů: použití třídy Mock z knihovny unittest.mock
https://www.root.cz/clanky/pouziti-pythonu-pro-tvorbu-testu-pouziti-tridy-mock-z-knihovny-unittest-mock/ - Použití nástroje pytest pro tvorbu jednotkových testů a benchmarků
https://www.root.cz/clanky/pouziti-nastroje-pytest-pro-tvorbu-jednotkovych-testu-a-benchmarku/ - Nástroj pytest a jednotkové testy: fixtures, výjimky, parametrizace testů
https://www.root.cz/clanky/nastroj-pytest-a-jednotkove-testy-fixtures-vyjimky-parametrizace-testu/ - Nástroj pytest a jednotkové testy: životní cyklus testů, užitečné tipy a triky
https://www.root.cz/clanky/nastroj-pytest-a-jednotkove-testy-zivotni-cyklus-testu-uzitecne-tipy-a-triky/ - Behavior-driven development v Pythonu s využitím knihovny Behave
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave/ - Behavior-driven development v Pythonu s využitím knihovny Behave (druhá část)
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave-druha-cast/ - Behavior-driven development v Pythonu s využitím knihovny Behave (závěrečná část)
https://www.root.cz/clanky/behavior-driven-development-v-pythonu-s-vyuzitim-knihovny-behave-zaverecna-cast/ - Validace datových struktur v Pythonu pomocí knihoven Schemagic a Schema
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-pomoci-knihoven-schemagic-a-schema/ - Validace datových struktur v Pythonu (2. část)
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-2-cast/ - Validace datových struktur v Pythonu (dokončení)
https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-dokonceni/ - Univerzální testovací nástroj Robot Framework
https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework/ - Univerzální testovací nástroj Robot Framework a BDD testy
https://www.root.cz/clanky/univerzalni-testovaci-nastroj-robot-framework-a-bdd-testy/ - Úvod do problematiky fuzzingu a fuzz testování
https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani/ - Úvod do problematiky fuzzingu a fuzz testování – složení vlastního fuzzeru
https://www.root.cz/clanky/uvod-do-problematiky-fuzzingu-a-fuzz-testovani-slozeni-vlastniho-fuzzeru/ - Knihovny a moduly usnadňující testování aplikací naprogramovaných v jazyce Clojure
https://www.root.cz/clanky/knihovny-a-moduly-usnadnujici-testovani-aplikaci-naprogramovanych-v-jazyce-clojure/ - Validace dat s využitím knihovny spec v Clojure 1.9.0
https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/ - Testování aplikací naprogramovaných v jazyce Go
https://www.root.cz/clanky/testovani-aplikaci-naprogramovanych-v-jazyce-go/ - Knihovny určené pro tvorbu testů v programovacím jazyce Go
https://www.root.cz/clanky/knihovny-urcene-pro-tvorbu-testu-v-programovacim-jazyce-go/ - Testování aplikací psaných v Go s využitím knihoven Goblin a Frisby
https://www.root.cz/clanky/testovani-aplikaci-psanych-v-go-s-vyuzitim-knihoven-goblin-a-frisby/ - Testování Go aplikací s využitím knihovny GΩmega a frameworku Ginkgo
https://www.root.cz/clanky/testovani-go-aplikaci-s-vyuzitim-knihovny-gomega-mega-a-frameworku-ginkgo/ - Tvorba BDD testů s využitím jazyka Go a nástroje godog
https://www.root.cz/clanky/tvorba-bdd-testu-s-vyuzitim-jazyka-go-a-nastroje-godog/ - Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem
https://www.root.cz/clanky/pouziti-go-pro-automatizaci-prace-s-aplikacemi-s-interaktivnim-prikazovym-radkem/ - Použití Go pro automatizaci práce s aplikacemi s interaktivním příkazovým řádkem (dokončení)
https://www.root.cz/clanky/pouziti-go-pro-automatizaci-prace-s-aplikacemi-s-interaktivnim-prikazovym-radkem-dokonceni/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure (2)
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure-2/
20. Odkazy na Internetu
- Shebang
https://en.wikipedia.org/wiki/Shebang_(Unix) - pytest 5.4.2 na PyPi
https://pypi.org/project/pytest/ - Awesome Python – testing
https://github.com/vinta/awesome-python#testing - pytest Plugins Compatibility
http://plugincompat.herokuapp.com/ - Selenium (pro Python)
https://pypi.org/project/selenium/ - Getting Started With Testing in Python
https://realpython.com/python-testing/ - unittest.mock — mock object library
https://docs.python.org/3.5/library/unittest.mock.html - mock 2.0.0
https://pypi.python.org/pypi/mock - An Introduction to Mocking in Python
https://www.toptal.com/python/an-introduction-to-mocking-in-python - Mock – Mocking and Testing Library
http://mock.readthedocs.io/en/stable/ - Python Mocking 101: Fake It Before You Make It
https://blog.fugue.co/2016–02–11-python-mocking-101.html - Nauč se Python! – Testování
http://naucse.python.cz/lessons/intro/testing/ - Flexmock (dokumentace)
https://flexmock.readthedocs.io/en/latest/ - Test Fixture (Wikipedia)
https://en.wikipedia.org/wiki/Test_fixture - Mock object (Wikipedia)
https://en.wikipedia.org/wiki/Mock_object - Extrémní programování
https://cs.wikipedia.org/wiki/Extr%C3%A9mn%C3%AD_programov%C3%A1n%C3%AD - Programování řízené testy
https://cs.wikipedia.org/wiki/Programov%C3%A1n%C3%AD_%C5%99%C3%ADzen%C3%A9_testy - Pip (dokumentace)
https://pip.pypa.io/en/stable/ - Tox
https://tox.readthedocs.io/en/latest/ - pytest: helps you write better programs
https://docs.pytest.org/en/latest/ - doctest — Test interactive Python examples
https://docs.python.org/dev/library/doctest.html#module-doctest - unittest — Unit testing framework
https://docs.python.org/dev/library/unittest.html - Python namespaces
https://bytebaker.com/2008/07/30/python-namespaces/ - Namespaces and Scopes
https://www.python-course.eu/namespaces.php - Stránka projektu Robot Framework
https://robotframework.org/ - GitHub repositář Robot Frameworku
https://github.com/robotframework/robotframework - Robot Framework (Wikipedia)
https://en.wikipedia.org/wiki/Robot_Framework - Tutoriál Robot Frameworku
http://www.robotframeworktutorial.com/ - Robot Framework Documentation
https://robotframework.org/robotframework/ - Robot Framework Introduction
https://blog.testproject.io/2016/11/22/robot-framework-introduction/ - robotframework 3.1.2 na PyPi
https://pypi.org/project/robotframework/ - Robot Framework demo (GitHub)
https://github.com/robotframework/RobotDemo - Robot Framework web testing demo using SeleniumLibrary
https://github.com/robotframework/WebDemo - Robot Framework for Mobile Test Automation Demo
https://www.youtube.com/watch?v=06LsU08slP8 - Gherkin
https://cucumber.io/docs/gherkin/ - Selenium
https://selenium.dev/ - SeleniumLibrary
https://robotframework.org/ - The Practical Test Pyramid
https://martinfowler.com/articles/practical-test-pyramid.html - Acceptance Tests and the Testing Pyramid
http://www.blog.acceptancetestdrivendevelopment.com/acceptance-tests-and-the-testing-pyramid/ - Tab-separated values
https://en.wikipedia.org/wiki/Tab-separated_values - A quick guide about Python implementations
https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321 - radamsa
https://gitlab.com/akihe/radamsa - Fuzzing (Wikipedia)
https://en.wikipedia.org/wiki/Fuzzing - american fuzzy lop
http://lcamtuf.coredump.cx/afl/ - Fuzzing: the new unit testing
https://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/fuzzing.slide#1 - Corpus for github.com/dvyukov/go-fuzz examples
https://github.com/dvyukov/go-fuzz-corpus - AFL – QuickStartGuide.txt
https://github.com/google/AFL/blob/master/docs/QuickStartGuide.txt - Introduction to Fuzzing in Python with AFL
https://alexgaynor.net/2015/apr/13/introduction-to-fuzzing-in-python-with-afl/ - Writing a Simple Fuzzer in Python
https://jmcph4.github.io/2018/01/19/writing-a-simple-fuzzer-in-python/ - How to Fuzz Go Code with go-fuzz (Continuously)
https://fuzzit.dev/2019/10/02/how-to-fuzz-go-code-with-go-fuzz-continuously/ - Golang Fuzzing: A go-fuzz Tutorial and Example
http://networkbit.ch/golang-fuzzing/ - Fuzzing Python Modules
https://stackoverflow.com/questions/20749026/fuzzing-python-modules - 0×3 Python Tutorial: Fuzzer
http://www.primalsecurity.net/0×3-python-tutorial-fuzzer/ - fuzzing na PyPi
https://pypi.org/project/fuzzing/ - Fuzzing 0.3.2 documentation
https://fuzzing.readthedocs.io/en/latest/ - Randomized testing for Go
https://github.com/dvyukov/go-fuzz - HTTP/2 fuzzer written in Golang
https://github.com/c0nrad/http2fuzz - Ffuf (Fuzz Faster U Fool) – An Open Source Fast Web Fuzzing Tool
https://hacknews.co/hacking-tools/20191208/ffuf-fuzz-faster-u-fool-an-open-source-fast-web-fuzzing-tool.html - Continuous Fuzzing Made Simple
https://fuzzit.dev/ - Halt and Catch Fire
https://en.wikipedia.org/wiki/Halt_and_Catch_Fire#Intel_x86 - Random testing
https://en.wikipedia.org/wiki/Random_testing - Monkey testing
https://en.wikipedia.org/wiki/Monkey_testing - Fuzzing for Software Security Testing and Quality Assurance, Second Edition
https://books.google.at/books?id=tKN5DwAAQBAJ&pg=PR15&lpg=PR15&q=%22I+settled+on+the+term+fuzz%22&redir_esc=y&hl=de#v=onepage&q=%22I%20settled%20on%20the%20term%20fuzz%22&f=false - libFuzzer – a library for coverage-guided fuzz testing
https://llvm.org/docs/LibFuzzer.html - fuzzy-swagger na PyPi
https://pypi.org/project/fuzzy-swagger/ - fuzzy-swagger na GitHubu
https://github.com/namuan/fuzzy-swagger - Fuzz testing tools for Python
https://wiki.python.org/moin/PythonTestingToolsTaxonomy#Fuzz_Testing_Tools - A curated list of awesome Go frameworks, libraries and software
https://github.com/avelino/awesome-go - gofuzz: a library for populating go objects with random values
https://github.com/google/gofuzz - tavor: A generic fuzzing and delta-debugging framework
https://github.com/zimmski/tavor - hypothesis na GitHubu
https://github.com/HypothesisWorks/hypothesis - Hypothesis: Test faster, fix more
https://hypothesis.works/ - Hypothesis
https://hypothesis.works/articles/intro/ - What is Hypothesis?
https://hypothesis.works/articles/what-is-hypothesis/ - Databáze CVE
https://www.cvedetails.com/ - Fuzz test Python modules with libFuzzer
https://github.com/eerimoq/pyfuzzer - Taof – The art of fuzzing
https://sourceforge.net/projects/taof/ - JQF + Zest: Coverage-guided semantic fuzzing for Java
https://github.com/rohanpadhye/jqf - http2fuzz
https://github.com/c0nrad/http2fuzz - Demystifying hypothesis testing with simple Python examples
https://towardsdatascience.com/demystifying-hypothesis-testing-with-simple-python-examples-4997ad3c5294 - Testování
http://voho.eu/wiki/testovani/ - Unit testing (Wikipedia.en)
https://en.wikipedia.org/wiki/Unit_testing - Unit testing (Wikipedia.cz)
https://cs.wikipedia.org/wiki/Unit_testing - Unit Test vs Integration Test
https://www.youtube.com/watch?v=0GypdsJulKE - TestDouble
https://martinfowler.com/bliki/TestDouble.html - Test Double
http://xunitpatterns.com/Test%20Double.html - Test-driven development (Wikipedia)
https://en.wikipedia.org/wiki/Test-driven_development - Acceptance test–driven development
https://en.wikipedia.org/wiki/Acceptance_test%E2%80%93driven_development - Gauge
https://gauge.org/ - Gauge (software)
https://en.wikipedia.org/wiki/Gauge_(software) - PYPL PopularitY of Programming Language
https://pypl.github.io/PYPL.html - Testing is Good. Pyramids are Bad. Ice Cream Cones are the Worst
https://medium.com/@fistsOfReason/testing-is-good-pyramids-are-bad-ice-cream-cones-are-the-worst-ad94b9b2f05f - Články a zprávičky věnující se Pythonu
https://www.root.cz/n/python/ - PythonTestingToolsTaxonomy
https://wiki.python.org/moin/PythonTestingToolsTaxonomy - Top 6 BEST Python Testing Frameworks [Updated 2020 List]
https://www.softwaretestinghelp.com/python-testing-frameworks/ - pytest-print 0.1.3
https://pypi.org/project/pytest-print/ - pytest fixtures: explicit, modular, scalable
https://docs.pytest.org/en/latest/fixture.html - PyTest Tutorial: What is, Install, Fixture, Assertions
https://www.guru99.com/pytest-tutorial.html - Pytest – Fixtures
https://www.tutorialspoint.com/pytest/pytest_fixtures.htm - Marking test functions with attributes
https://docs.pytest.org/en/latest/mark.html - pytest-print
https://pytest-print.readthedocs.io/en/latest/ - Continuous integration
https://en.wikipedia.org/wiki/Continuous_integration - Travis CI
https://travis-ci.org/