pytest
pytest testing framework conventions and practices. Invoke whenever task involves any interaction with pytest — writing tests, configuring pytest, fixtures, parametrize, mocking, debugging test failures, or coverage.
What this skill does
# pytest
**Test behavior, not implementation. Tests are executable documentation — if the test name doesn't explain what the code
does, rewrite it.**
pytest is Python's standard testing framework. It uses plain `assert` statements, fixtures for setup/teardown, and a
rich plugin ecosystem. All patterns target Python 3.14+.
## References
- **Fixture patterns, scope, factories, teardown** — [`${CLAUDE_SKILL_DIR}/references/fixtures.md`]: Fixture lifecycle,
yield fixtures, factory pattern, request object, parametrized fixtures
- **Parametrize patterns, indirect, IDs** — [`${CLAUDE_SKILL_DIR}/references/parametrize.md`]: Multi-parameter examples,
indirect fixtures, custom IDs, stacking decorators
- **Monkeypatch patterns, scoped patches** — [`${CLAUDE_SKILL_DIR}/references/monkeypatch.md`]: API overview,
attribute/env/dict patching, scoped monkeypatch, common recipes
- **Plugin ecosystem and configuration** — [`${CLAUDE_SKILL_DIR}/references/plugins.md`]: pytest-asyncio, pytest-mock,
pytest-xdist, pytest-cov configuration patterns
## Test Structure
### Discovery and Naming
- **Files:** `test_*.py` or `*_test.py`. Prefer `test_<module>.py` matching source module.
- **Functions:** `test_<behavior>` — describe the behavior, not the method: `test_returns_empty_list_when_no_matches`
not `test_search`.
- **Classes:** `TestClassName` groups related tests. No `__init__` method. Use classes when tests share setup; use bare
functions for independent tests.
- **conftest.py** is auto-discovered — no import needed. Place shared fixtures at the appropriate directory level.
### Arrange-Act-Assert
Structure every test in three phases:
```python
def test_user_creation_sets_defaults():
# Arrange
data = {"name": "Alice", "email": "[email protected]"}
# Act
user = User.from_dict(data)
# Assert
assert user.name == "Alice"
assert user.is_active is True
assert user.roles == []
```
- **One act per test.** If you need multiple acts, write multiple tests.
- **Comments optional** when phases are obvious. Add when the test is long enough that phases aren't immediately clear.
### Test Granularity
- **One concept per test.** Multiple assertions are fine when they verify the same behavior. Separate tests when
behaviors are independent.
- **Fast by default.** Unit tests should run in milliseconds. Gate slow tests (network, DB) behind markers:
`@pytest.mark.slow`.
- **Isolation is mandatory.** Tests must not depend on execution order or shared mutable state. Each test sets up its
own world.
## Fixtures
### Core Rules
- **Fixtures over setup methods.** Fixtures are composable, scoped, and explicit. Never use `setUp`/`tearDown` from
`unittest`.
- **Explicit injection.** Request fixtures by name in test parameters. Every dependency is visible in the test
signature.
- **Smallest viable scope.** Default is `function` scope (fresh per test). Use broader scopes (`class`, `module`,
`session`) only for expensive resources.
- **`autouse=True` sparingly.** Only for setup that genuinely applies to every test in scope (e.g., database transaction
rollback, temp directory cleanup).
### Yield Fixtures (Setup + Teardown)
```python
@pytest.fixture
def db_connection():
conn = create_connection()
yield conn
conn.close()
@pytest.fixture
def temp_config(tmp_path: Path):
config_file = tmp_path / "config.toml"
config_file.write_text('[app]\ndebug = true\n')
yield config_file
# cleanup automatic — tmp_path handles it
```
- **`yield`** separates setup from teardown. Code after `yield` runs even if the test fails.
- **Prefer `yield`** over `addfinalizer` — clearer control flow.
- **Teardown must not raise.** If cleanup can fail, wrap in `try`/`except` and log.
### Fixture Factories
When tests need multiple instances with varying configuration:
```python
@pytest.fixture
def make_user():
def _make_user(name: str = "Alice", *, active: bool = True) -> User:
return User(name=name, is_active=active)
return _make_user
def test_inactive_users_excluded(make_user):
active = make_user("Alice", active=True)
inactive = make_user("Bob", active=False)
assert filter_active([active, inactive]) == [active]
```
### Fixture Scope
- **`function`** — Each test (default). Most fixtures — cheap setup, isolation.
- **`class`** — All tests in a class. Shared expensive setup within a test class.
- **`module`** — All tests in a file. Database connection per test file.
- **`session`** — Entire test run. Server startup, heavy resource initialization.
- **Session-scoped fixtures** must be in `conftest.py` at the root test directory.
- **Don't mix scopes carelessly.** A function-scoped fixture cannot depend on a function-scoped fixture that modifies
state from a broader scope.
### Built-in Fixtures
- **`tmp_path`** — `Path` to a temporary directory unique to the test (function scope)
- **`tmp_path_factory`** — Factory for creating temp directories (session scope)
- **`capsys`** — Capture `sys.stdout`/`sys.stderr` writes
- **`capfd`** — Capture file descriptor 1/2 output (catches C-level writes)
- **`caplog`** — Capture `logging` output with access to records
- **`monkeypatch`** — Dynamic attribute/env/dict patching with automatic restore
- **`request`** — Fixture metadata: `.param`, `.node`, `.config`, `.fspath`
- **`pytestconfig`** — Access to the pytest config object
See `${CLAUDE_SKILL_DIR}/references/fixtures.md` for fixture lifecycle details, parametrized fixtures, and advanced
patterns.
## Parametrize
### Basic Usage
```python
@pytest.mark.parametrize("input_val, expected", [
("hello", 5),
("", 0),
(" spaces ", 10),
])
def test_string_length(input_val: str, expected: int):
assert len(input_val) == expected
```
- **Use descriptive IDs:** `pytest.param("", 0, id="empty-string")` for readable output.
- **Each row is a distinct test.** Failures report which parameter combination failed.
### Stacking Decorators
```python
@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [10, 20])
def test_combinations(x: int, y: int):
assert x + y > 0
# Generates: (1,10), (1,20), (2,10), (2,20)
```
### Indirect Parametrize
Pass parameter values to fixtures instead of directly to the test:
```python
@pytest.fixture
def user(request) -> User:
return User(name=request.param)
@pytest.mark.parametrize("user", ["Alice", "Bob"], indirect=True)
def test_user_greeting(user: User):
assert user.name in user.greet()
```
See `${CLAUDE_SKILL_DIR}/references/parametrize.md` for multi-parameter patterns, conditional skipping within
parametrize, and dynamic parametrize generation.
## Markers
### Built-in Markers
- **`@pytest.mark.skip(reason="...")`** — unconditionally skip.
- **`@pytest.mark.skipif(condition, reason="...")`** — skip when condition is true:
`@pytest.mark.skipif(sys.platform == "win32", reason="Unix only")`.
- **`@pytest.mark.xfail(reason="...")`** — expected failure. Passes if the test fails, reports unexpected pass if it
succeeds. Use `strict=True` to fail on unexpected pass.
- **`@pytest.mark.usefixtures("fixture_name")`** — inject fixture without using its value.
- **`@pytest.mark.filterwarnings("ignore::DeprecationWarning")`** — per-test warning filter.
### Custom Markers
Register in `pyproject.toml` to avoid warnings:
```toml
[tool.pytest.ini_options]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks integration tests",
]
```
```python
@pytest.mark.slow
def test_full_pipeline():
...
```
Run subsets: `pytest -m "not slow"`, `pytest -m "integration and not slow"`.
## Mocking
### monkeypatch (Preferred for Simple Cases)
```python
def test_reads_env_variable(monkeypatch):
monkeypatch.setenv("API_KEY", "test-key")
assert get_api_key() == "test-key"
def test_overrides_attribute(monkeypatch):
monkeypatch.setattr("myapp.config.DEBUG", True)
assert is_debug_mode() is TrueRelated in Productivity
gitea-workflow
IncludedOrchestrate agile development workflows for Gitea repositories using the tea CLI. Use when working with Gitea-hosted repos and asking to 'run the workflow', 'continue working', 'what's next', 'complete the task cycle', 'start my day', 'end the sprint', 'implement the next task', or wanting guided step-by-step development assistance. Keywords: workflow, orchestrate, agile, task cycle, sprint, daily, implement, review, PR, standup, retrospective, gitea, tea.
microsoft-graph-gateway
IncludedRoute Microsoft Graph work in this workspace. Use when users want to read or write Outlook mail, calendar events, contacts, OneDrive or SharePoint files, Teams, Planner, To Do, users, groups, directory data, or arbitrary Microsoft Graph endpoints from VS Code. Prefer WorkIQ for common read scenarios. Use Microsoft Graph for write actions and gap-read scenarios that need exact Graph properties, filters, permissions, or endpoints.
copilotkit
IncludedUse when building with CopilotKit — setup, development, integrations, debugging, upgrading, or contributing. Routes to the appropriate specialized skill based on the task.
wordly-wisdom
IncludedProvides calibrated decision analysis using Charlie Munger-style multiple mental models, inversion, incentive mapping, circle-of-competence checks, misjudgment audits, second-order effects, and forecast updates. Use when the user asks for an oracle take, a hard call, a decision memo, a premortem, an outside view, a red-team, a sanity-check, what am I missing, think this through, or wants a strategy, hire, investment, plan, product, partnership, or major life choice analysed. Avoid for simple factual lookups or time-sensitive legal, medical, or market questions without fresh evidence.
swain-session
IncludedSession management and project status dashboard. Owns the full session lifecycle (start/work/close/resume), focus lane, bookmarks, worktree detection, and tab naming. Also serves as the project status dashboard — shows active epics, progress, actionable next steps, blocked items, tasks, GitHub issues, and recommendations. Worktree creation is deferred to swain-do task dispatch (SPEC-195). Triggers on: 'session', 'status', 'what's next', 'dashboard', 'overview', 'where are we', 'what should I work on', 'show me priorities', 'bookmark', 'focus on', 'session info'.
gandi
IncludedComprehensive Gandi domain registrar integration for domain and DNS management. Register and manage domains, create/update/delete DNS records (A, AAAA, CNAME, MX, TXT, SRV, and more), configure email forwarding and aliases, check SSL certificate status, create DNS snapshots for safe rollback, bulk update zone files, and monitor domain expiration. Supports multi-domain management, zone file import/export, and automated DNS backups. Includes both read-only and destructive operations with safety controls.