Pytest Testing Guide
Write Python tests efficiently with pytest.
Quick Start
pip install pytest
# test_example.py
def test_addition():
assert 1 + 1 == 2
Run: pytest or pytest -v
Core Concepts
| Concept |
Example |
| Test function |
def test_something(): |
| Assertion |
assert result == expected |
| Fixture |
@pytest.fixture - dependency injection |
| Marker |
@pytest.mark.skip - test metadata |
| Parametrize |
@pytest.mark.parametrize - multiple inputs |
Essential Patterns
Basic Test
def test_function():
result = my_function(1, 2)
assert result == 3
Exception Testing
import pytest
def test_raises():
with pytest.raises(ValueError, match="invalid"):
raise ValueError("invalid input")
Fixture (Setup/Teardown)
@pytest.fixture
def database():
db = create_db()
yield db # provide to test
db.cleanup() # teardown
def test_query(database):
assert database.query("SELECT 1")
Parametrize
@pytest.mark.parametrize("input,expected", [
(1, 2),
(2, 4),
(3, 6),
])
def test_double(input, expected):
assert input * 2 == expected
Mocking
def test_mock(monkeypatch):
monkeypatch.setattr("module.func", lambda: "mocked")
monkeypatch.setenv("API_KEY", "test-key")
Reference Files
| Topic |
File |
When to Use |
| Basics |
basics.md |
Test discovery, assertions, running tests, config |
| Fixtures |
fixtures.md |
Setup/teardown, scopes, factories, conftest.py |
| Markers |
markers.md |
skip, xfail, parametrize, custom markers |
| Mocking |
mocking.md |
monkeypatch, pytest-mock, spies, async mocks |
| Plugins |
plugins.md |
Coverage, parallel, async, Django, Flask, FastAPI |
Common Commands
pytest # Run all tests
pytest -v # Verbose
pytest -s # Show prints
pytest -x # Stop on first failure
pytest -k "pattern" # Run matching tests
pytest -m slow # Run by marker
pytest --cov=mypackage # With coverage
pytest -n auto # Parallel (xdist)
pytest --lf # Run last failed
pytest --pdb # Debug on failure
Project Structure
project/
├── src/
│ └── mypackage/
│ └── module.py
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── test_unit.py
│ └── test_integration.py
├── pytest.ini # Or pyproject.toml
└── requirements-test.txt
Configuration (pyproject.toml)
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tb=short"
markers = [
"slow: slow tests",
"integration: integration tests",
]
Built-in Fixtures
| Fixture |
Purpose |
tmp_path |
Temp directory (pathlib.Path) |
tmpdir |
Temp directory (legacy) |
capsys |
Capture stdout/stderr |
caplog |
Capture logging |
monkeypatch |
Patch attributes/env vars |
request |
Test/fixture metadata |
Fixture Scopes
@pytest.fixture(scope="function") # Default - per test
@pytest.fixture(scope="class") # Per test class
@pytest.fixture(scope="module") # Per file
@pytest.fixture(scope="session") # Per test run
Best Practices
- Name tests descriptively:
test_user_login_with_invalid_password_fails
- One assertion concept per test
- Use fixtures for reusable setup
- Use
conftest.py for shared fixtures
- Use markers to categorize tests (unit, integration, slow)
- Run with coverage:
pytest --cov --cov-report=html
- Use parametrize to reduce duplicate tests