Python Testing Process
PythonTestingPytestLintingType Checking
Description
QA every edit with pytest
Globs
tests/**/*.py
---
description: QA every edit with pytest
globs: tests/**/*.py
---
# Python Testing Process
## Project Stack
The project uses the following tools and technologies:
- **uv** - Python package management and virtual environments
- **ruff** - Fast Python linter and formatter
- **pytest** - Testing framework
- **mypy** - Static type checking
- **doctest** - Testing code examples in documentation
## 1. Start with Formatting
Format your code first:
```
uv run ruff format .
```
## 2. Run Tests
Verify that your changes pass the tests:
```
# Run all tests
uv run pytest
# Run with verbose output
uv run pytest -v
# Run tests with coverage
uv run pytest --cov=src
```
### Running Specific Tests
Focus on testing exactly what you changed:
```
# Run tests in a specific file
uv run pytest tests/path/to/test_file.py
# Run a specific test class
uv run pytest tests/path/to/test_file.py::TestClass
# Run a specific test method
uv run pytest tests/path/to/test_file.py::TestClass::test_method
# Run tests matching a pattern
uv run pytest -k "pattern"
# Run tests with verbose, exit first failure, and no capture
uv run pytest -vxs tests/path/to/test_file.py
# Run tests with debugging tools
uv run pytest --pdb tests/path/to/test_file.py
```
### Additional Pytest Features
```
# Generate test report with JUnit XML format
uv run pytest --junitxml=results.xml
# Show test durations to identify slow tests
uv run pytest --durations=10
# Run tests that previously failed
uv run pytest --last-failed
# Run with debugger on failures
uv run pytest --pdb
```
## 3. Commit Initial Changes
Make an atomic commit for your changes using conventional commits.
Use `@git-commits.mdc` for assistance with commit message standards.
## 4. Run Linting and Type Checking
Check and fix linting issues:
```
uv run ruff check . --fix --show-fixes
```
Check typings:
```
uv run mypy
```
## 5. Verify Tests Again
Ensure tests still pass after linting and type fixes:
```
uv run pytest
```
## 6. Final Commit
Make a final commit with any linting/typing fixes.
Use `@git-commits.mdc` for assistance with commit message standards.
## Development Loop Guidelines
If there are any failures at any step due to your edits, fix them before proceeding to the next step.
## Python Testing Standards
### Writing Effective Tests
1. **Test Independence**:
- Each test should run independently of others
- Use fixtures for setup and teardown
- Avoid test interdependencies
2. **Naming Conventions**:
- Test files: `test_*.py`
- Test classes: `Test*`
- Test functions: `test_*`
- Use descriptive names that indicate what's being tested
3. **Test Structure**:
- Arrange: Set up test conditions
- Act: Perform the action being tested
- Assert: Verify the expected outcomes
### Docstring Guidelines
For `src/**/*.py` files, follow these docstring guidelines:
1. **Use reStructuredText format** for all docstrings.
```python
"""Short description of the function or class.
Detailed description using reStructuredText format.
Parameters
----------
param1 : type
Description of param1
param2 : type
Description of param2
Returns
-------
type
Description of return value
"""
```
2. **Keep the main description on the first line** after the opening `"""`.
3. **Use NumPy docstyle** for parameter and return value documentation.
For test files, follow these docstring guidelines:
1. **Use reStructuredText format** for all docstrings.
```python
"""Test module for example functionality.
This module contains tests for:
- Feature A
- Feature B
"""
```
2. **Document test purpose**:
```python
def test_example_function():
"""Test that example_function handles valid inputs correctly.
Verifies:
- Result formatting
- Error handling
- Edge cases
"""
```
### Doctest Guidelines
For doctests in `src/**/*.py` files:
1. **Use narrative descriptions** for test sections rather than inline comments:
```python
"""Example function.
Examples
--------
Create an instance:
>>> obj = ExampleClass()
Verify a property:
>>> obj.property
'expected value'
"""
```
2. **Move complex examples** to dedicated test files at `tests/examples/<path_to_module>/test_<example>.py` if they require elaborate setup or multiple steps.
3. **Utilize pytest fixtures** via `doctest_namespace` for more complex test scenarios:
```python
"""Example with fixture.
Examples
--------
>>> # doctest_namespace contains all pytest fixtures from conftest.py
>>> example_fixture = getfixture('example_fixture')
>>> example_fixture.method()
'expected result'
"""
```
4. **Keep doctests simple and focused** on demonstrating usage rather than comprehensive testing.
5. **Add blank lines between test sections** for improved readability.
6. **Test your doctests** with pytest:
```
# Run doctests for specific module
uv run pytest --doctest-modules src/path/to/module.py
```
### Pytest Fixtures and Testing Guidelines
1. **Use existing fixtures over mocks**:
- Use fixtures from conftest.py instead of `monkeypatch` and `MagicMock` when available
- For instance, if using libtmux, use provided fixtures: `server`, `session`, `window`, and `pane`
- Document in test docstrings why standard fixtures weren't used for exceptional cases
2. **Preferred pytest patterns**:
- Use `tmp_path` (pathlib.Path) fixture over Python's `tempfile`
- Use `monkeypatch` fixture over `unittest.mock`
- Use parameterized tests for multiple test cases
```python
@pytest.mark.parametrize("input_val,expected", [
(1, 2),
(2, 4),
(3, 6)
])
def test_double(input_val, expected):
assert double(input_val) == expected
```
3. **Iterative testing workflow**:
- Always test each specific change immediately after making it
- Run the specific test that covers your change
- Fix issues before moving on to the next change
- Run broader test collections only after specific tests pass
### Import Guidelines
1. **Prefer namespace imports**:
- Import modules and access attributes through the namespace instead of importing specific symbols
- Example: Use `import enum` and access `enum.Enum` instead of `from enum import Enum`
- This applies to standard library modules like `pathlib`, `os`, and similar cases
2. **Standard aliases**:
- For `typing` module, use `import typing as t`
- Access typing elements via the namespace: `t.NamedTuple`, `t.TypedDict`, etc.
- Note primitive types like unions can be done via `|` pipes and primitive types like list and dict can be done via `list` and `dict` directly.
3. **Benefits of namespace imports**:
- Improves code readability by making the source of symbols clear
- Reduces potential naming conflicts
- Makes import statements more maintainable
### Import Guidelines for Test Files
1. **Standard imports for tests**:
```python
import pytest
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from _pytest.capture import CaptureFixture
from _pytest.fixtures import FixtureRequest
from _pytest.logging import LogCaptureFixture
from _pytest.monkeypatch import MonkeyPatch
from pytest_mock.plugin import MockerFixture
```
2. **Organize imports clearly**:
- Standard library imports first
- Third-party imports second
- Application imports third
- Test fixtures and utilities last
3. **Use type annotations** in all test functions:
```python
def test_with_fixtures(
tmp_path: Path,
monkeypatch: MonkeyPatch,
caplog: LogCaptureFixture
) -> None:
"""Test with properly typed fixtures."""
```