Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Pytest Testing

ASecurity

pytest testing patterns for any Python project: test structure, fixtures, parametrize, conftest.py, monkeypatch, tmp_path, markers, unittest.mock (MagicMock, patch), pytest-cov coverage, and best practices. Stack-agnostic — referenced by every Python plugin in the marketplace. Use this skill to: - Organise tests with conftest.py shared fixtures and a clear test/src layout. - Write parametrized tests to cover multiple inputs without duplication. - Mock external dependencies (HTTP, DB, filesys...

35 stars
0 votes
0 copies
0 views
Added 9/22/2026
developmentpythongobashsqlexpressfastapidjangoflaskdockertesting

Works with

cliapi

Security Analysis

A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add AratKruglik/claude-sdlc --skill pytest-testing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pytest Testing?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Pytest Testing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aratkruglik-pytest-testing/badge)](https://www.skillsdirectory.com/skills/aratkruglik-pytest-testing)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: pytest-testing
description: |
  pytest testing patterns for any Python project: test structure, fixtures, parametrize, conftest.py, monkeypatch, tmp_path, markers, unittest.mock (MagicMock, patch), pytest-cov coverage, and best practices. Stack-agnostic — referenced by every Python plugin in the marketplace.

  Use this skill to:
  - Organise tests with conftest.py shared fixtures and a clear test/src layout.
  - Write parametrized tests to cover multiple inputs without duplication.
  - Mock external dependencies (HTTP, DB, filesystem) with unittest.mock and monkeypatch.
  - Measure and enforce code coverage with pytest-cov.

  Do NOT use this skill for:
  - Framework-specific test types (Django TestCase / WebTestCase, FastAPI TestClient, Flask test client — see framework plugin skills).
  - Language idioms — see python-foundation:python-conventions.
user-invocable: false
paths: ["tests/**", "**/test_*.py", "**/*_test.py", "**/conftest.py"]
---

# pytest Testing Patterns (stack-agnostic)

pytest is the standard test runner for modern Python projects. These patterns apply to any Python 3.10+ codebase regardless of framework.

## Project layout

Keep tests outside the source tree so installed packages are tested rather than the raw source directory:

```
src/
  myapp/
    __init__.py
    service.py
    repository.py
tests/
  conftest.py
  test_service.py
  test_repository.py
  integration/
    conftest.py
    test_db.py
pyproject.toml
```

Configure pytest to find the source in `pyproject.toml`:

```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--strict-markers -q"
```

Install the package in editable mode before running tests so `import myapp` resolves correctly:

```bash
pip install -e .        # or: poetry install / uv sync
pytest
```

## conftest.py — shared fixtures

`conftest.py` files are loaded automatically by pytest. Place shared fixtures there instead of importing them into every test module.

```python
# tests/conftest.py
from __future__ import annotations

import pytest

from myapp.database import Database, get_connection
from myapp.service import UserService


@pytest.fixture(scope="module")
def db() -> Database:
    """Module-scoped DB connection — created once per test module."""
    conn = get_connection(url="sqlite:///:memory:")
    conn.create_tables()
    yield conn
    conn.close()


@pytest.fixture
def user_service(db: Database) -> UserService:
    """Function-scoped service — fresh instance per test, shared DB."""
    return UserService(db)
```

Fixture scopes (from broadest to narrowest):

| Scope | Created | Destroyed |
|---|---|---|
| `session` | Once per `pytest` run | After all tests |
| `module` | Once per test module | After last test in module |
| `class` | Once per test class | After last test in class |
| `function` (default) | Once per test function | After the test returns |

Use the narrowest scope that avoids expensive re-creation. Use `session` scope only for truly global state (e.g., a Docker container started via `pytest-docker`).

## Basic test patterns

```python
# tests/test_service.py
from __future__ import annotations

import pytest

from myapp.service import UserService
from myapp.exceptions import NotFoundError


def test_create_user_returns_new_id(user_service: UserService) -> None:
    user_id = user_service.create(name="Alice", email="alice@example.com")
    assert user_id > 0


def test_find_user_by_id(user_service: UserService) -> None:
    user_id = user_service.create(name="Bob", email="bob@example.com")
    user = user_service.find(user_id)
    assert user.name == "Bob"
    assert user.email == "bob@example.com"


def test_find_missing_user_raises(user_service: UserService) -> None:
    with pytest.raises(NotFoundError):
        user_service.find(99999)


def test_find_missing_user_message(user_service: UserService) -> None:
    with pytest.raises(NotFoundError, match="User.*99999"):
        user_service.find(99999)
```

Use plain `assert` — pytest rewrites assertions to produce detailed failure messages. Do not use `assertEqual` / `assertTrue` / `assertRaises` (those are `unittest.TestCase` methods).

## Fixtures — yield, teardown, autouse

Use `yield` in a fixture to run teardown code after the test:

```python
@pytest.fixture
def temp_user(user_service: UserService) -> int:
    user_id = user_service.create(name="Temp", email="temp@example.com")
    yield user_id
    user_service.delete(user_id)   # teardown: always runs even if test fails
```

Fixtures can depend on other fixtures — pytest resolves the dependency graph automatically:

```python
@pytest.fixture
def admin_user(user_service: UserService) -> User:
    user_id = user_service.create(name="Admin", email="admin@example.com")
    return user_service.promote_to_admin(user_id)
```

Use `autouse=True` for cross-cutting setup that every test in a module or package needs:

```python
# tests/integration/conftest.py
@pytest.fixture(autouse=True)
def rollback_after_test(db: Database) -> None:
    """Wrap every integration test in a transaction that rolls back."""
    db.begin()
    yield
    db.rollback()
```

## parametrize — data-driven tests

Drive many input/output combinations through a single test body:

```python
import pytest


@pytest.mark.parametrize("amount,currency,expected_str", [
    (100, "USD", "1.00 USD"),
    (0,   "EUR", "0.00 EUR"),
    (999, "GBP", "9.99 GBP"),
])
def test_money_format(amount: int, currency: str, expected_str: str) -> None:
    money = Money(amount, currency)
    assert str(money) == expected_str
```

Use `pytest.param` to give test cases readable IDs and to mark individual cases:

```python
@pytest.mark.parametrize("raw,expected", [
    pytest.param("draft",    Status.DRAFT,    id="draft"),
    pytest.param("active",   Status.ACTIVE,   id="active"),
    pytest.param("archived", Status.ARCHIVED, id="archived"),
])
def test_status_parsing(raw: str, expected: Status) -> None:
    assert Status(raw) == expected


@pytest.mark.parametrize("raw", [
    pytest.param("unknown", id="unknown-value"),
    pytest.param("",        id="empty-string"),
])
def test_status_invalid(raw: str) -> None:
    with pytest.raises(ValueError):
        Status(raw)
```

Multiple independent parameters are passed as separate arguments — pytest generates all combinations:

```python
@pytest.mark.parametrize("x", [0, 1, 2])
@pytest.mark.parametrize("y", [10, 20])
def test_add(x: int, y: int) -> None:
    assert add(x, y) == x + y   # 6 test cases generated
```

## monkeypatch — patching at test time

`monkeypatch` is a pytest fixture that temporarily modifies attributes, environment variables, or dictionary entries and automatically restores them after the test.

```python
import requests


def get_user_count(api_url: str) -> int:
    response = requests.get(f"{api_url}/users/count")
    response.raise_for_status()
    return response.json()["count"]


def test_get_user_count(monkeypatch: pytest.MonkeyPatch) -> None:
    class FakeResponse:
        def raise_for_status(self) -> None: ...
        def json(self) -> dict:
            return {"count": 42}

    monkeypatch.setattr(requests, "get", lambda url: FakeResponse())

    count = get_user_count("https://api.example.com")
    assert count == 42
```

Other common `monkeypatch` uses:

```python
def test_reads_env_var(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setenv("DATABASE_URL", "sqlite:///:memory:")
    assert get_database_url() == "sqlite:///:memory:"


def test_missing_env_var(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.delenv("DATABASE_URL", raising=False)
    with pytest.raises(EnvironmentError):
        get_database_url()


def test_patches_dict(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setitem(config, "timeout", 5)
    assert effective_timeout() == 5
```

Prefer `monkeypatch` over `unittest.mock.patch` for patching attributes and environment variables in pytest — it integrates with pytest's fixture lifecycle and does not require a context manager or decorator.

## unittest.mock — mocking objects and calls

Use `unittest.mock` when you need to assert that a collaborator was called with specific arguments, or to make a mock raise an exception.

```python
from unittest.mock import MagicMock, patch

from myapp.service import OrderService


def test_sends_confirmation_email() -> None:
    mailer = MagicMock()
    gateway = MagicMock()
    gateway.charge.return_value = Receipt(id="rcpt_1", amount=500)

    service = OrderService(payment_gateway=gateway, mailer=mailer)
    service.checkout(order=make_order(total_cents=500))

    gateway.charge.assert_called_once_with(500)
    mailer.send.assert_called_once()


def test_checkout_raises_on_payment_failure() -> None:
    gateway = MagicMock()
    gateway.charge.side_effect = PaymentDeclinedError("insufficient funds")

    service = OrderService(payment_gateway=gateway, mailer=MagicMock())

    with pytest.raises(PaymentDeclinedError, match="insufficient funds"):
        service.checkout(order=make_order(total_cents=500))
```

Use `patch` as a context manager when you need to replace a name in a module's namespace:

```python
from unittest.mock import patch


def test_sends_welcome_email() -> None:
    with patch("myapp.service.send_email") as mock_send:
        create_user(name="Alice", email="alice@example.com")
        mock_send.assert_called_once_with(
            to="alice@example.com",
            subject="Welcome",
        )
```

Or as a decorator:

```python
@patch("myapp.service.send_email")
def test_email_on_signup(mock_send: MagicMock) -> None:
    create_user(name="Bob", email="bob@example.com")
    assert mock_send.call_count == 1
```

Patch the name where it is **used**, not where it is defined. If `myapp.service` imports `send_email` from `myapp.email`, patch `myapp.service.send_email`, not `myapp.email.send_email`.

## tmp_path — tests that write files

Use the built-in `tmp_path` fixture (a `pathlib.Path` pointing to a unique temporary directory) for tests that need to read or write files. pytest cleans it up after the session.

```python
from pathlib import Path
import pytest

from myapp.export import write_csv


def test_write_csv_creates_file(tmp_path: Path) -> None:
    output = tmp_path / "report.csv"
    rows = [{"name": "Alice", "score": "95"}, {"name": "Bob", "score": "87"}]

    write_csv(output, rows)

    assert output.exists()
    lines = output.read_text(encoding="utf-8").splitlines()
    assert lines[0] == "name,score"
    assert len(lines) == 3   # header + 2 data rows


def test_load_config_from_file(tmp_path: Path) -> None:
    config_file = tmp_path / "config.toml"
    config_file.write_text('[app]\nport = 8080\n', encoding="utf-8")

    config = load_config(config_file)

    assert config.port == 8080
```

`tmp_path` is function-scoped by default. Use `tmp_path_factory` (session fixture) if multiple fixtures or tests need to share the same temporary directory.

## Coverage — measuring and enforcing

Run pytest with `pytest-cov`:

```bash
pytest --cov=src --cov-report=term-missing
```

Configure coverage in `pyproject.toml`:

```toml
[tool.coverage.run]
source = ["src"]
branch = true               # track branch coverage, not just line coverage

[tool.coverage.report]
fail_under = 80
show_missing = true
omit = ["src/myapp/migrations/*", "src/myapp/__main__.py"]
```

With `fail_under = 80`, `pytest --cov=src` exits non-zero if coverage drops below 80% — useful as a CI gate.

Generate an HTML report for local inspection:

```bash
pytest --cov=src --cov-report=html
open htmlcov/index.html
```

Coverage is a floor, not a goal. A covered line is not necessarily an asserted behaviour — 100% coverage does not mean 0 bugs. Focus on asserting meaningful behaviours; the coverage number confirms you did not miss entire code paths.

## Running tests

```bash
pytest                          # run all tests
pytest -x                       # stop on first failure
pytest -v                       # verbose: show each test name
pytest -q                       # quiet: summary only
pytest -k "test_create"         # run tests whose name matches the expression
pytest tests/test_service.py    # run a specific module
pytest tests/integration/       # run a subdirectory
pytest --tb=short               # shorter tracebacks
pytest --lf                     # run only tests that failed last time
pytest -n auto                  # parallel execution (requires pytest-xdist)
```

Run the full quality pipeline before committing:

```bash
ruff format --check . && ruff check . && mypy src/ && pytest --cov=src
```

Attribution

AratKruglikAratKruglik
View sourceMore from AratKruglik →
SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284722 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2192 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →