pytest Basics
pytest is the most popular testing framework in Python. It is simple, powerful, and has a large plugin ecosystem.
Install pytest
uv add --dev pytest
Writing Your First Test
# tests/test_math.py
def add(a: int, b: int) -> int:
return a + b
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -2) == -3
def test_add_zero():
assert add(5, 0) == 5
Running Tests
uv run pytest # run all tests
uv run pytest tests/test_math.py # run specific file
uv run pytest -v # verbose output
uv run pytest -k "test_add" # run tests matching name
uv run pytest --tb=short # shorter error messages
Assertions
pytest uses Python's built-in assert statement:
def test_assertions_example():
# Equality
assert 1 + 1 == 2
# Truthiness
assert True
assert [1, 2, 3] # non-empty list is truthy
# Containment
assert "hello" in "hello world"
assert 5 in [1, 2, 3, 4, 5]
# Comparison
assert 10 > 5
assert len([1, 2, 3]) == 3
# Type checking
assert isinstance(42, int)
Checking for Exceptions
import pytest
def test_division_by_zero():
with pytest.raises(ZeroDivisionError):
result = 1 / 0
def test_invalid_input():
with pytest.raises(ValueError, match="invalid"):
int("invalid")
Test Discovery
pytest automatically finds tests when:
- File names start with
test_or end with_test.py - Function names start with
test_ - Class names start with
Test
tests/
├── test_user.py ✓ found
├── test_api.py ✓ found
├── user_test.py ✓ found
├── helpers.py ✗ skipped
└── conftest.py ✓ special file for fixtures
Test Classes
Group related tests in a class:
class TestUserValidation:
def test_valid_email(self):
assert is_valid_email("user@example.com")
def test_invalid_email_no_at(self):
assert not is_valid_email("userexample.com")
def test_invalid_email_empty(self):
assert not is_valid_email("")
Useful Command-Line Options
| Option | What It Does |
|---|---|
-v |
Verbose — show each test name |
-s |
Show print statements |
-x |
Stop after first failure |
--lf |
Run only last failed tests |
--ff |
Run failed tests first |
-k "name" |
Run tests matching name pattern |
--tb=short |
Short traceback |
--co |
Collect only (list tests without running) |
conftest.py
A special file where you put shared fixtures and hooks:
# tests/conftest.py
import pytest
@pytest.fixture
def sample_user() -> dict:
return {"name": "Alice", "email": "alice@example.com"}
Fixtures in conftest.py are available to all tests in the same directory and below.
Best Practices
- Name test files with
test_prefix - Name test functions with
test_prefix - Use descriptive names that explain the test
- Keep each test short and focused
- Use
pytest.raisesto test exceptions - Use
-vflag to see detailed test output - Put shared fixtures in
conftest.py