mcpField
P
pytest-coder

pytest-coder

github.com/majesticlabs-devテスト・品質5050 回インストール7 週間前に更新
リポジトリ

pytest-coder について

pytest-coderは、pytestを使った保守しやすいPythonテストの設計と作成を支援するスキルです。振る舞いの検証、分離、フィクスチャ、パラメータ化、モック、非同期テスト、境界ケースを重視します。

主な機能

  1. 01Arrange-Act-Assertのテスト構成
  2. 02フィクスチャとプロジェクト構成
  3. 03パラメータ化テストとマーカー
  4. 04外部依存のモック
  5. 05非同期処理と例外のテスト
  6. 06分離性と境界ケースの確認

活用シーン

  1. 01ユニットテストと統合テストの整理
  2. 02パラメータ化で複数の入力をテスト
  3. 03I/Oをモックしてユニットテストを高速化
  4. 04非同期コードと例外をテスト
  5. 05テストの分離性と境界ケースを確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/majesticlabs-dev/majestic-marketplace --skill pytest-coder

インストール後、Claude Code で/pytest-coderを入力して読み込まれたか確認します。

詳細

Pytest コーダー

中核となる理念

原則適用方法
AAA パターンすべてのテストで Arrange-Act-Assert を実施
実装より振る舞いコードの動作をテストし、実装方法はテストしません
独立性テストは互いに独立している必要があります
高速なテストI/O をモックし、データベースへのアクセスを最小限にします
説明的な名前テスト名でシナリオを説明します
カバレッジ正常系とエッジケースの両方をテストします

プロジェクト構成

tests/
├── conftest.py          # Shared fixtures
├── unit/                # Unit tests (fast, isolated)
│   ├── test_models.py
│   └── test_services.py
├── integration/         # Integration tests (real dependencies)
│   └── test_api.py
└── fixtures/            # Test data files
    └── sample_data.json

基本的なパターン

基本的なテスト構造
import pytest
from myapp.services import UserService

class TestUserService:
    """Tests for UserService."""

    def test_create_user_with_valid_data(self, user_service):
        # Arrange
        user_data = {"email": "[email protected]", "name": "Test User"}

        # Act
        result = user_service.create(user_data)

        # Assert
        assert result.email == "[email protected]"
        assert result.id is not None

    def test_create_user_with_duplicate_email_raises_error(self, user_service, existing_user):
        # Arrange
        user_data = {"email": existing_user.email, "name": "Another User"}

        # Act & Assert
        with pytest.raises(ValueError, match="Email already exists"):
            user_service.create(user_data)
フィクスチャ
# conftest.py
import pytest
from myapp.database import get_db
from myapp.services import UserService

@pytest.fixture
def db():
    """Provide a clean database session."""
    session = get_db()
    yield session
    session.rollback()

@pytest.fixture
def user_service(db):
    """Provide UserService instance."""
    return UserService(db)

@pytest.fixture
def sample_user():
    """Provide sample user data."""
    return {"email": "[email protected]", "name": "Test User", "password": "secret123"}

@pytest.fixture
def existing_user(db, sample_user):
    """Create and return an existing user."""
    from myapp.models import User
    user = User(**sample_user)
    db.add(user)
    db.commit()
    return user
パラメーター化テスト
import pytest

@pytest.mark.parametrize("input_email,expected_valid", [
    ("[email protected]", True),
    ("[email protected]", True),
    ("invalid-email", False),
    ("missing@domain", False),
    ("", False),
])
def test_email_validation(input_email, expected_valid):
    from myapp.validators import is_valid_email
    assert is_valid_email(input_email) == expected_valid

@pytest.mark.parametrize("status,expected_message", [
    ("pending", "Order is being processed"),
    ("shipped", "Order has been shipped"),
    ("delivered", "Order has been delivered"),
], ids=["pending-status", "shipped-status", "delivered-status"])
def test_order_status_message(status, expected_message):
    from myapp.orders import get_status_message
    assert get_status_message(status) == expected_message
モック
from unittest.mock import Mock, patch, AsyncMock

def test_send_email_calls_smtp(user_service):
    # Mock external dependency
    with patch("myapp.services.smtp_client") as mock_smtp:
        mock_smtp.send.return_value = True

        user_service.send_welcome_email("[email protected]")

        mock_smtp.send.assert_called_once_with(
            to="[email protected]",
            subject="Welcome!",
        )

def test_payment_processing_handles_failure():
    mock_gateway = Mock()
    mock_gateway.charge.side_effect = PaymentError("Card declined")

    service = PaymentService(gateway=mock_gateway)

    with pytest.raises(PaymentError):
        service.process_payment(amount=100)
非同期テスト
import pytest

@pytest.mark.asyncio
async def test_async_fetch_user(user_service):
    # Arrange
    user_id = 1

    # Act
    user = await user_service.get_by_id(user_id)

    # Assert
    assert user.id == user_id

@pytest.fixture
async def async_db():
    """Async database session fixture."""
    from myapp.database import async_session
    async with async_session() as session:
        yield session
        await session.rollback()

# Mock async functions
@pytest.mark.asyncio
async def test_async_external_api():
    with patch("myapp.client.fetch_data", new_callable=AsyncMock) as mock_fetch:
        mock_fetch.return_value = {"status": "ok"}

        result = await fetch_and_process()

        assert result["status"] == "ok"
例外のテスト
import pytest

def test_divide_by_zero_raises_error():
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

def test_invalid_input_raises_with_message():
    with pytest.raises(ValueError, match="must be positive"):
        process_amount(-100)

def test_exception_attributes():
    with pytest.raises(CustomError) as exc_info:
        risky_operation()

    assert exc_info.value.code == "E001"
    assert "failed" in str(exc_info.value)

フィクスチャのスコープ

スコープライフサイクル使用例
functionテストごと(デフォルト)ほとんどのフィクスチャ
classテストクラスごとクラス内で共有するセットアップ
moduleモジュールごとモジュール内で共有するコストの高いセットアップ
sessionテスト実行全体データベース接続、サーバー
@pytest.fixture(scope="session")
def database_engine():
    """Create engine once for entire test session."""
    engine = create_engine(TEST_DATABASE_URL)
    yield engine
    engine.dispose()

@pytest.fixture(scope="function")
def db_session(database_engine):
    """Create fresh session per test."""
    connection = database_engine.connect()
    transaction = connection.begin()
    session = Session(bind=connection)

    yield session

    session.close()
    transaction.rollback()
    connection.close()

マーカー

# pytest.ini or pyproject.toml
[tool.pytest.ini_options]
markers = [
    "slow: marks tests as slow",
    "integration: marks integration tests",
    "unit: marks unit tests",
]

# Usage
@pytest.mark.slow
def test_complex_calculation():
    ...

@pytest.mark.integration
def test_database_connection():
    ...

# Run specific markers
# pytest -m "not slow"
# pytest -m "unit"

品質チェックリスト

  • すべてのテストで AAA パターン(Arrange-Act-Assert)を使用する
  • シナリオを説明する、説明的なテスト名を付ける
  • 共通のセットアップにはフィクスチャを使用する
  • 複数の入力にはパラメーター化テストを使用する
  • 外部依存関係にはモックを使用する
  • 正常系をテストする
  • エラーケースをテストする
  • エッジケースを網羅する
  • 非同期テストでは @pytest.mark.asyncio を使用する
  • テスト間に依存関係がない
  • カバレッジが 90% を超えている

アンチパターン

アンチパターン問題点修正方法
テストが実行順序に依存する不安定で、デバッグが困難ですフィクスチャを使用し、分離します
実装をテストするテストが壊れやすくなります振る舞いをテストします
アサーションが多すぎる失敗箇所を特定しにくくなりますテストごとにアサーションを 1 つにします
エラーケースのテストがないカバレッジが不足します例外を明示的にテストします
遅い単体テストフィードバックが遅くなりますI/O をモックし、インメモリ DB を使用します

コメント