Строй надёжные тесты на pytest: внедрение фикстур, очистка через yield, табличная параметризация и ловушки широких областей видимости.
Открыть этот урок в Kodokonpytest держится на двух сильных идеях: обнаружение тестов по соглашению (файлы test_*.py, функции test_*) и обычный assert, интроспекция которого при провале показывает реальные значения обеих сторон - больше не нужно запоминать assertEqual и три десятка его вариантов. Два инструмента, которые стоит узнать сразу: pytest.raises, чтобы проверить, что исключение действительно возникло, и pytest.approx, чтобы сравнивать числа с плавающей точкой, не воюя с двоичным округлением.
import pytest
def divide(a: float, b: float) -> float:
return a / b
def test_nominal() -> None:
assert divide(8, 4) == 2.0
def test_zero_division() -> None:
with pytest.raises(ZeroDivisionError):
divide(1, 0)
def test_floats() -> None:
assert 0.1 + 0.2 == pytest.approx(0.3)Фикстуры - это внедрение зависимостей в pytest: объяви параметр с именем фикстуры, и pytest создаст её, а затем передаст в тест. yield разделяет подготовку и очистку - код после него выполняется, даже если тест провалился. Параметр scope (function по умолчанию, module, session) задаёт время жизни объекта. Общие фикстуры складывай в conftest.py: они становятся доступны во всей папке, без единого импорта.
import sqlite3
import pytest
@pytest.fixture
def db():
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT)")
yield conn
conn.close()
def test_insert(db) -> None:
db.execute("INSERT INTO users VALUES ('Ada')")
query = "SELECT name FROM users"
rows = db.execute(query).fetchall()
assert rows == [("Ada",)]@pytest.mark.parametrize превращает тест в таблицу случаев: каждый кортеж становится отдельным тестом с собственным именем, который можно фильтровать по отдельности. Ставь несколько декораторов parametrize друг над другом, чтобы получить декартово произведение параметров. Давай своим случаям читаемые идентификаторы через pytest.param(..., id='...') - отчёт о провале должен быть понятен без открытия исходного файла.
import pytest
def slugify(text: str) -> str:
return "-".join(text.lower().split())
@pytest.mark.parametrize(
("raw", "expected"),
[
("Hello World", "hello-world"),
(" Python rocks ", "python-rocks"),
pytest.param("", "", id="empty"),
],
)
def test_slugify(raw: str, expected: str) -> None:
assert slugify(raw) == expectedpytest -x --lf
pytest -k "slugify and not empty"
pytest -q --durations=5yield?pytest --lf?