Pytest is a powerful, flexible testing framework for Python that makes it easy to write simple and scalable test cases.
Pytest provides:
- A simple, expressive syntax for writing tests
- Powerful fixture system for test setup and teardown
- Comprehensive assertion introspection
- Plugin architecture for extending functionality
- Parallel test execution
- Test discovery and collection
Pytest and its plugins are included as development dependencies:
# Install with other development dependencies
uv sync --devTo install it directly:
uv pip install pytest pytest-cov pytest-asyncio pytest-xdistIn this project, Pytest is used to:
- Write and run unit tests
- Measure code coverage
- Test asynchronous code
- Run tests in parallel
- Generate coverage reports
Pytest is configured in the pyproject.toml file:
[tool.pytest.ini_options]
asyncio_default_fixture_loop_scope = "function"
python_classes = ["Test*"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
testpaths = ["tests"]Pytest is also configured as a poethepoet task for running tests with coverage:
[tool.poe.tasks]
test-coverage = "pytest -xvs -n auto --cov=src --cov-report=xml --cov-fail-under=80 --ignore=tests"To run tests:
# Run all tests with coverage
uv run poe test-coverage
# Run all tests
uv run pytest
# Run specific tests
uv run pytest tests/unit/test_specific.py
# Run tests matching a pattern
uv run pytest -k "test_pattern"# Run tests verbosely
uv run pytest -v
# Show extra test summary info
uv run pytest -v
# Stop after first failure
uv run pytest -x
# Drop into debugger on failure
uv run pytest --pdb
# Run tests in parallel
uv run pytest -n auto
# Measure code coverage
uv run pytest --cov=src# test_example.py
def test_addition():
assert 1 + 1 == 2
def test_string_methods():
assert "hello".capitalize() == "Hello"# test_example.py
class TestMathFunctions:
def test_addition(self):
assert 1 + 1 == 2
def test_multiplication(self):
assert 2 * 3 == 6# test_example.py
import pytest
@pytest.fixture
def sample_data():
return {"name": "Test User", "age": 30}
def test_user_name(sample_data):
assert sample_data["name"] == "Test User"
def test_user_age(sample_data):
assert sample_data["age"] == 30# test_example.py
import pytest
def divide(a, b):
return a / b
def test_division_by_zero():
with pytest.raises(ZeroDivisionError):
divide(1, 0)# test_example.py
import pytest
@pytest.mark.parametrize(
"input,expected",
[
(1, 1),
(2, 4),
(3, 9),
(4, 16),
],
)
def test_square(input, expected):
assert input**2 == expected# test_example.py
import pytest
@pytest.mark.asyncio
async def test_async_function():
result = await some_async_function()
assert result == expected_valueMeasures code coverage and generates reports:
uv run pytest --cov=src --cov-report=xmlEnables testing of asynchronous code:
@pytest.mark.asyncio
async def test_async_function():
# Test async codeEnables parallel test execution:
uv run pytest -n auto # Use all available CPU cores
uv run pytest -n 4 # Use 4 CPU cores- Follow naming conventions: Name test files with
test_prefix and test functions withtest_prefix. - Use fixtures for setup and teardown: Avoid duplicating setup code across tests.
- Keep tests independent: Tests should not depend on the state from other tests.
- Test one thing per test: Each test should verify a single behavior.
- Use parameterized tests: Test multiple inputs with a single test function.
- Aim for high coverage: Strive for at least 80% code coverage.
- Include both positive and negative tests: Test both expected and error cases.
If tests aren't being discovered:
- Check that test files start with
test_ - Check that test functions start with
test_ - Check that test classes start with
Test - Check the
testpathsconfiguration
If you're having issues with fixtures:
- Check fixture scope (function, class, module, session)
- Check for circular dependencies between fixtures
- Ensure fixtures are accessible to the tests that need them
If you're having issues with coverage:
- Check that the source paths are correct
- Ensure you're not excluding relevant files
- Check for
.coveragercor configuration inpyproject.toml