Programování

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.

12. 10. 2026 · 4 min čtení

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 monkeypatch nebo knihovnu respx (pro httpx).
  • pytest --lf spustí 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.