Pytest základy: jak začít testovat Python kód
Praktický úvod do pytestu – první test, assert, fixtures, parametrizace, testování výjimek, monkeypatch, tmp_path a měření pokrytí kódu.
Pytest je nejpoužívanější testovací framework pro Python. Oproti vestavěnému unittest píšete méně kódu: žádné třídy, žádné self.assertEqual, jen funkce a obyčejný assert. Projdeme si vše, co potřebujete pro první reálné testy.
Instalace a první test
pip install pytest
Máme modul kalkulacka.py:
def vydel(a: float, b: float) -> float:
if b == 0:
raise ValueError("Dělení nulou")
return a / b
def sleva(cena: float, procenta: int) -> float:
return round(cena * (1 - procenta / 100), 2)
A test test_kalkulacka.py:
from kalkulacka import vydel, sleva
def test_vydel():
assert vydel(10, 2) == 5
def test_sleva():
assert sleva(200, 25) == 150.0
Spuštění:
pytest # najde a spustí testy
pytest -v # podrobný výpis
pytest -x # zastaví se na první chybě
pytest -k sleva # jen testy, jejichž název obsahuje "sleva"
Pytest automaticky hledá soubory test_*.py nebo *_test.py a v nich funkce začínající test.
Proč stačí obyčejný assert
Pytest přepisuje assert při importu testů, takže při selhání ukáže porovnávané hodnoty:
> assert sleva(200, 25) == 140.0
E assert 150.0 == 140.0
E + where 150.0 = sleva(200, 25)
U slovníků a seznamů dokonce zobrazí rozdíl po položkách.
Porovnávání desetinných čísel
import pytest
def test_float():
assert 0.1 + 0.2 == pytest.approx(0.3)
Přímé 0.1 + 0.2 == 0.3 je False kvůli reprezentaci floatů.
Testování výjimek
def test_deleni_nulou():
with pytest.raises(ValueError, match="nulou"):
vydel(1, 0)
match je regulární výraz hledaný v textu výjimky.
Parametrizace
Místo kopírování téhož testu s jinými hodnotami:
@pytest.mark.parametrize("cena, procenta, ocekavano", [
(100, 0, 100.0),
(100, 10, 90.0),
(99.99, 15, 84.99),
(50, 100, 0.0),
])
def test_sleva_param(cena, procenta, ocekavano):
assert sleva(cena, procenta) == ocekavano
Každá sada se hlásí jako samostatný test, takže hned vidíte, která kombinace selhala.
Fixtures
Fixture je funkce, která připraví data nebo prostředí pro test. Test si ji vyžádá jménem parametru:
import sqlite3
import pytest
@pytest.fixture
def db():
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
yield conn # sem se předá řízení testu
conn.close() # úklid po testu
def test_vlozeni(db):
db.execute("INSERT INTO users (name) VALUES ('Jana')")
assert db.execute("SELECT count(*) FROM users").fetchone()[0] == 1
Fixtures sdílené více soubory dejte do conftest.py – pytest je najde automaticky, nic se neimportuje.
Parametr scope určuje životnost: function (výchozí), module, session. Drahé zdroje jako testovací databázový server vytvářejte se scope="session".
Užitečné vestavěné fixtures
tmp_path – dočasný adresář jako pathlib.Path:
def test_zapis(tmp_path):
soubor = tmp_path / "data.txt"
soubor.write_text("ahoj", encoding="utf-8")
assert soubor.read_text(encoding="utf-8") == "ahoj"
monkeypatch – dočasná změna proměnných prostředí, atributů nebo funkcí:
import os
def nacti_klic():
return os.environ["API_KEY"]
def test_klic(monkeypatch):
monkeypatch.setenv("API_KEY", "test123")
assert nacti_klic() == "test123"
Po testu se vše vrátí do původního stavu.
capsys – zachycení výstupu print:
def test_vypis(capsys):
print("Hotovo")
assert capsys.readouterr().out == "Hotovo\n"
Struktura projektu
projekt/
├── pyproject.toml
├── src/
│ └── mojeapp/
│ └── kalkulacka.py
└── tests/
├── conftest.py
└── test_kalkulacka.py
Konfigurace v pyproject.toml:
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
pythonpath = ["src"]
Volba pythonpath (pytest 7+) přidá src do cesty importů, takže v testech funguje from mojeapp.kalkulacka import sleva.
Pokrytí kódu
pip install pytest-cov
pytest --cov=mojeapp --cov-report=term-missing
Výpis ukáže řádky, které žádný test nespustil. Stoprocentní pokrytí není cíl samo o sobě – ale neotestovaná větev except je dobrý tip, kam se podívat.
Tipy na závěr
- Jeden test = jedna věc. Název by měl říkat, co se ověřuje:
test_sleva_nad_100_procent_vyhodi_chybu. - Testy nesmí záviset na pořadí. Každý si připraví vlastní data přes fixtures.
- Netestujte síť. Externí API nahraďte přes
monkeypatchnebo knihovnurespx(pro httpx). pytest --lfspustí jen testy, které minule selhaly – ideální při opravování.
Pokud máte hotový modul bez testů, nahrajte ho do ColdHost AI v režimu Programování a požádejte o návrh parametrizovaných testů pro okrajové případy – pak je jen projděte a doplňte, co zná jen vaše doména.