Structure robust tests with pytest: fixture injection, yield-based teardown, table-driven parametrization and the pitfalls of broad scopes.
Open this lesson in Kodokonpytest rests on two strong ideas: discovery by convention (test_*.py files, test_* functions) and the plain assert, whose introspection displays the actual values of both sides on failure - no more memorizing assertEqual and its thirty variants. Two tools to know right away: pytest.raises to check that an exception is indeed raised, and pytest.approx to compare floats without fighting binary rounding.
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)Fixtures are pytest's dependency injection: declare a parameter bearing the name of a fixture, and pytest builds it then injects it. The yield separates setup from teardown - the code after it runs even if the test fails. The scope parameter (function by default, module, session) controls the object's lifetime. Put shared fixtures in a conftest.py: they become available throughout the folder, without a single import.
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 turns a test into a table of cases: each tuple becomes an independent test, named and filterable individually. Stack several parametrize decorators to get the cartesian product of the parameters. Give your cases readable ids via pytest.param(..., id='...') - a failure report should be understandable without opening the source file.
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 for?pytest --lf do?