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."""
   ```