本文へ移動
cccskills
無料GitHub で公開

pytest-testing

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.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md12.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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:

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

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

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.

# 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):

ScopeCreatedDestroyed
sessionOnce per pytest runAfter all tests
moduleOnce per test moduleAfter last test in module
classOnce per test classAfter last test in class
function (default)Once per test functionAfter 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

# 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:

@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:

@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:

# 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:

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:

@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:

@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.

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:

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.

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:

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:

@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.

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:

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

Configure coverage in pyproject.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:

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

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:

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

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する