Agent Skills by ALSEL
汎用ソフトウェア開発⭐ リポ 2品質スコア 64/100

testing-patterns

pytest のパターン、フィクスチャ、モッキング、プロパティベーステスト、非同期テスト、カバレッジ分析、および LLM 固有のテスト戦略に対応しています。

description の原文を見る

pytest patterns, fixtures, mocking, property-based testing, async tests, coverage analysis, and LLM-specific testing strategies

SKILL.md 本文

テストパターン

Pytest設定

pyproject.toml

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
    "integration: marks integration tests",
    "e2e: marks end-to-end tests",
    "llm: marks tests that call LLM APIs (real or mocked)",
]
filterwarnings = [
    "error",
    "ignore::DeprecationWarning:some_library.*",
]

カバレッジ設定

[tool.coverage.run]
branch = true
source = ["src"]
omit = ["tests/*", "*/migrations/*"]

[tool.coverage.report]
fail_under = 80
show_missing = true
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "if __name__ == .__main__.",
    "@overload",
]

フィクスチャパターン

スコープ付きフィクスチャ

import pytest

@pytest.fixture(scope="session")
def db_engine():
    """One engine for the entire test session."""
    engine = create_engine("sqlite:///:memory:")
    yield engine
    engine.dispose()

@pytest.fixture(scope="function")
def db_session(db_engine):
    """Fresh transaction per test, rolled back after."""
    connection = db_engine.connect()
    transaction = connection.begin()
    session = Session(bind=connection)
    yield session
    session.close()
    transaction.rollback()
    connection.close()

ファクトリフィクスチャ

@pytest.fixture
def make_user(db_session):
    """Factory fixture for creating test users."""
    created = []
    def _make_user(name="test", email=None, **kwargs):
        email = email or f"{name}@test.com"
        user = User(name=name, email=email, **kwargs)
        db_session.add(user)
        db_session.flush()
        created.append(user)
        return user
    yield _make_user
    for user in created:
        db_session.delete(user)

一時パスフィクスチャ

@pytest.fixture
def config_dir(tmp_path):
    """Temporary config directory with default files."""
    config = tmp_path / "config"
    config.mkdir()
    (config / "settings.yaml").write_text("debug: true\n")
    return config

モッキングパターン

LLM APIモッキング

@pytest.fixture
def mock_llm_response(mocker):
    """Mock LLM API with realistic response structure."""
    def _mock(content="test response", tokens=50, model="gpt-4"):
        mock = mocker.patch("litellm.completion")
        mock.return_value = MockResponse(
            choices=[MockChoice(message=MockMessage(content=content))],
            usage=MockUsage(
                prompt_tokens=tokens,
                completion_tokens=tokens * 2,
                total_tokens=tokens * 3,
            ),
            model=model,
        )
        return mock
    return _mock


def test_llm_chain_returns_parsed(mock_llm_response):
    mock_llm_response(content='{"name": "test", "score": 42}')
    result = my_chain.invoke({"query": "test"})
    assert result.name == "test"
    assert result.score == 42

構造化出力モッキング

@pytest.fixture
def mock_structured_output(mocker):
    """Mock with_structured_output for Pydantic models."""
    def _mock(model_class, **field_values):
        instance = model_class(**field_values)
        mock_chain = mocker.MagicMock()
        mock_chain.invoke.return_value = instance
        return mock_chain
    return _mock

環境変数モッキング

def test_loads_api_key_from_env(monkeypatch):
    monkeypatch.setenv("OPENAI_API_KEY", "test-key")
    config = load_config()
    assert config.api_key == "test-key"

def test_raises_without_api_key(monkeypatch):
    monkeypatch.delenv("OPENAI_API_KEY", raising=False)
    with pytest.raises(ConfigError, match="OPENAI_API_KEY"):
        load_config()

非同期テスト

import pytest
from unittest.mock import AsyncMock

@pytest.mark.asyncio
async def test_async_pipeline():
    """Test async LLM pipeline."""
    mock_provider = AsyncMock()
    mock_provider.acomplete.return_value = "test response"
    
    result = await pipeline.arun(provider=mock_provider, query="test")
    assert result.status == "success"
    mock_provider.acomplete.assert_awaited_once()


@pytest.mark.asyncio
async def test_timeout_handling():
    """Verify timeout raises appropriately."""
    import asyncio
    
    async def slow_response(*args, **kwargs):
        await asyncio.sleep(10)
    
    mock_provider = AsyncMock(side_effect=slow_response)
    with pytest.raises(asyncio.TimeoutError):
        await asyncio.wait_for(
            pipeline.arun(provider=mock_provider, query="test"),
            timeout=1.0,
        )

プロパティベーステスト

from hypothesis import given, settings, strategies as st

@given(st.text(min_size=1, max_size=100))
def test_tokenizer_roundtrip(text):
    """Encoding then decoding should return original text."""
    tokens = tokenizer.encode(text)
    decoded = tokenizer.decode(tokens)
    assert decoded == text


@given(st.lists(st.integers(min_value=0, max_value=100), min_size=1))
@settings(max_examples=500)
def test_chunk_sizes_sum_to_total(sizes):
    """Chunking preserves total token count."""
    text = " ".join(["word"] * sum(sizes))
    chunks = chunk_text(text, max_tokens=50)
    total = sum(len(c.split()) for c in chunks)
    assert total == sum(sizes)

パラメータ化パターン

@pytest.mark.parametrize("model,expected_provider", [
    ("gpt-4", "openai"),
    ("claude-3-sonnet", "anthropic"),
    ("ollama/llama3", "ollama"),
    ("unknown-model", None),
])
def test_model_routing(model, expected_provider):
    provider = resolve_provider(model)
    assert provider == expected_provider


@pytest.mark.parametrize("input_text,expected_error", [
    ("", "Input cannot be empty"),
    ("x" * 10001, "Input exceeds maximum length"),
    ("<script>alert(1)</script>", "Input contains disallowed HTML"),
])
def test_input_validation_rejects_bad_input(input_text, expected_error):
    with pytest.raises(ValidationError, match=expected_error):
        validate_input(input_text)

避けるべきテストのアンチパターン

  1. 実装の詳細をテストする — 内部状態ではなく、動作をテストしましょう
  2. 脆弱なアサーション — 正確なログメッセージやタイムスタンプをアサートしないでください
  3. テスト間の依存関係 — 各テストは独立して、任意の順序で実行できなければなりません
  4. 過度なモッキング — すべてをモックしてしまえば、何もテストしていません
  5. エッジケースの見落とし — 空の入力、Noneの値、境界条件を考慮しましょう
  6. 不安定なテストを無視する — 修正するか隔離するか、決して無視しないでください
  7. 第三者ライブラリのテスト — 境界をモックして、彼らのライブラリをテストしないでください

CI統合

# .github/workflows/test.yml (抜粋)
jobs:
  test:
    steps:
      - run: uv run pytest tests/unit -x --cov --cov-report=xml
      - run: uv run pytest tests/integration -x -m "not slow"
      - run: uv run pytest tests/e2e -x --timeout=120

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
pvliesdonk
リポジトリ
pvliesdonk/agents.md
ライセンス
MIT
最終更新
2026/3/21

Source: https://github.com/pvliesdonk/agents.md / ライセンス: MIT

本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: pvliesdonk · pvliesdonk/agents.md · ライセンス: MIT