---
name: write-python-tests-with-pytest
description: Writes clear, fast Python tests with pytest - arrange-act-assert structure, fixtures in conftest.py, parametrize for many cases, mocking only at the edges, and coverage reports. Use when the user wants to add Python tests, set up pytest, test a function or API, or fix flaky or slow Python tests.
license: MIT
compatibility: Needs Python 3.9+ and pytest
metadata:
  category: debugging
  tags: "python,testing"
  author: johnny-bravo
  version: "1.0"
  updated: "2026-11-02"
  status: published
  aliases: "pytest, python unit tests, conftest fixtures"
  source_url: "https://github.com/wshobson/agents/tree/7e2fb559cf4b824b27f1fec49fe88f7511066d26/plugins/python-development/skills/python-testing-patterns"
  source_license: MIT
  copyright: "Copyright (c) 2024 Seth Hobson"
  original_author: "Seth Hobson (wshobson/agents, python-testing-patterns)"
  modified: "Adapted by TheSkillVault: rewritten as a short beginner workflow with original examples; dropped the long pattern catalogue; added safety notes, gotchas and done-when."
  imported_on: "2026-11-02"
---

# Write Python tests with pytest

pytest finds files named `test_*.py` and runs every function that starts with `test_`. Plain `assert` is all you need.

## When to use
- Your Python code has no tests and you want a safety net.
- You need to test a FastAPI, Flask or Django endpoint.
- Tests are flaky, slow or hard to read.

## When not to use
- You want to write the test before the code as a habit. Use test-first-with-tdd, then come back for pytest details.

## Steps
1. Install: `pip install pytest pytest-cov` (add `pytest-asyncio` for async code).
2. Make a `tests/` folder that mirrors your package, with a `conftest.py` for shared fixtures.
3. Write each test as arrange, act, assert. One behaviour per test, named for it: `test_refund_fails_when_order_is_shipped`.
4. Use fixtures for setup you repeat, such as a test database or client. Keep their scope as small as works.
5. Use `@pytest.mark.parametrize` to run one test over many inputs.
6. Check errors with `with pytest.raises(ValueError):`.
7. Mock only at the edges: network calls, time, payment providers. Use `monkeypatch` or `unittest.mock`.
8. Run `pytest -q`, then `pytest --cov=yourpackage --cov-report=term-missing` to see untested lines.
9. Add the test command to CI.

```python
import pytest
from shop.pricing import apply_discount

@pytest.mark.parametrize("price, code, expected", [(100, "TEN", 90), (100, None, 100)])
def test_apply_discount(price, code, expected):
    assert apply_discount(price, code) == expected
```

## Gotchas
- Tests that share state leak into each other. Use fixtures, not module-level globals.
- Never point tests at a production database or real API keys. Use a test database and fake credentials.
- Over-mocking tests your mocks, not your code. Prefer real objects inside your own code.
- Use `tmp_path` for files, not real folders.

## Portability
Works with any Python project and framework. If you use unittest today, pytest runs those tests too, so you can switch gradually instead of rewriting.

## Example prompt
"Add pytest tests for my pricing module, including edge cases, and show me the coverage."

## Done when
- `pytest` passes locally and in CI.
- Each important behaviour and error path has a named test.
- No test touches real services, real money or real secrets.

---
Adapted from "python-testing-patterns" in wshobson/agents (MIT, Copyright (c) 2024 Seth Hobson). See LICENSE.txt.
