mcpField
T
testing-python

testing-python

github.com/prefecthqテスト・品質28.3k122 回インストール1 日前に更新
リポジトリ

testing-python について

Python開発者向けのテスト作成スキルで、非同期テストやインメモリトランスポートを含む、プロジェクト固有の規約と実行方法を示します。

主な機能

  1. 01単一の動作を検証するテストの作成
  2. 02関連する入力差分のパラメーター化
  3. 03テストでのインメモリトランスポート利用
  4. 04フィクスチャとモックの指針
  5. 05記載された pytest コマンドでのテスト実行

活用シーン

  1. 01単一の動作を軸にしたユニットテストの構成
  2. 02非同期マーカーなしでの非同期コードのテスト
  3. 03tmp_path を使ったファイル操作の検証
  4. 04例外処理や外部サービス呼び出しの検証

インストール

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

$npx skills add https://github.com/prefecthq/fastmcp --skill testing-python

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

詳細

効果的な Python テストの作成

基本原則

各テストはアトミックかつ自己完結型で、単一の機能をテストする必要があります。複数のことをテストするテストは、デバッグや保守が難しくなります。

テストの構成

アトミックな単体テスト

各テストでは、単一の動作を検証します。失敗したときに何が壊れているかがわかるよう、テスト名を付けてください。すべてが同じ動作を検証する場合は、複数のアサーションを使用してもかまいません。

# Good: Name tells you what's broken
def test_user_creation_sets_defaults():
    user = User(name="Alice")
    assert user.role == "member"
    assert user.id is not None
    assert user.created_at is not None

# Bad: If this fails, what behavior is broken?
def test_user():
    user = User(name="Alice")
    assert user.role == "member"
    user.promote()
    assert user.role == "admin"
    assert user.can_delete_others()
同じ概念のバリエーションにはパラメーター化を使用する
import pytest

@pytest.mark.parametrize("input,expected", [
    ("hello", "HELLO"),
    ("World", "WORLD"),
    ("", ""),
    ("123", "123"),
])
def test_uppercase_conversion(input, expected):
    assert input.upper() == expected
機能が異なる場合はテストを分ける

無関係な動作をパラメーター化しないでください。テストのロジックが異なる場合は、別々のテストを作成してください。

プロジェクト固有のルール

非同期マーカーは不要

このプロジェクトでは、グローバルに asyncio_mode = "auto" を使用しています。デコレーターを付けずに非同期テストを記述してください。

# Correct
async def test_async_operation():
    result = await some_async_function()
    assert result == expected

# Wrong - don't add this
@pytest.mark.asyncio
async def test_async_operation():
    ...
モジュールレベルでインポートする

すべてのインポートをファイルの先頭に記述してください。

# Correct
import pytest
from fastmcp import FastMCP
from fastmcp.client import Client

async def test_something():
    mcp = FastMCP("test")
    ...

# Wrong - no local imports
async def test_something():
    from fastmcp import FastMCP  # Don't do this
    ...
テストにはメモリー内トランスポートを使用する

FastMCP サーバーをクライアントに直接渡してください。

from fastmcp import FastMCP
from fastmcp.client import Client

mcp = FastMCP("TestServer")

@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {name}!"

async def test_greet_tool():
    async with Client(mcp) as client:
        result = await client.call_tool("greet", {"name": "World"})
        assert result[0].text == "Hello, World!"

ネットワーク機能を明示的にテストする場合にのみ、HTTP トランスポートを使用してください。

複雑なデータにはインラインスナップショットを使用する

JSON スキーマや複雑な構造のテストには、inline-snapshot を使用してください。

from inline_snapshot import snapshot

def test_schema_generation():
    schema = generate_schema(MyModel)
    assert schema == snapshot()  # Will auto-populate on first run

コマンド:

  • pytest --inline-snapshot=create - 空のスナップショットを作成する
  • pytest --inline-snapshot=fix - 意図的な変更後に更新する

フィクスチャ

関数スコープのフィクスチャを優先する
@pytest.fixture
def client():
    return Client()

async def test_with_client(client):
    result = await client.ping()
    assert result is not None
ファイル操作には tmp_path を使用する
def test_file_writing(tmp_path):
    file = tmp_path / "test.txt"
    file.write_text("content")
    assert file.read_text() == "content"

モック

境界でモックする
from unittest.mock import patch, AsyncMock

async def test_external_api_call():
    with patch("mymodule.external_client.fetch", new_callable=AsyncMock) as mock:
        mock.return_value = {"data": "test"}
        result = await my_function()
        assert result == {"data": "test"}
自分が所有するものをモックしない

可能な場合は、実際の実装を使ってコードをテストしてください。モックするのは外部サービスであり、内部クラスではありません。

テストの命名

シナリオがわかる、説明的な名前を使用してください。

# Good
def test_login_fails_with_invalid_password():
def test_user_can_update_own_profile():
def test_admin_can_delete_any_user():

# Bad
def test_login():
def test_update():
def test_delete():

エラーのテスト

import pytest

def test_raises_on_invalid_input():
    with pytest.raises(ValueError, match="must be positive"):
        calculate(-1)

async def test_async_raises():
    with pytest.raises(ConnectionError):
        await connect_to_invalid_host()

テストの実行

uv run pytest -n auto              # Run all tests in parallel
uv run pytest -n auto -x           # Stop on first failure
uv run pytest path/to/test.py      # Run specific file
uv run pytest -k "test_name"       # Run tests matching pattern
uv run pytest -m "not integration" # Exclude integration tests

チェックリスト

テストを提出する前に、以下を確認してください。

  • 各テストが1つのことをテストしている
  • @pytest.mark.asyncio デコレーターを使用していない
  • モジュールレベルでインポートしている
  • 説明的なテスト名を付けている
  • ネットワークをテストする場合を除き、HTTP ではなくメモリー内トランスポートを使用している
  • 同じ動作のバリエーションにはパラメーター化を使用している
  • 動作が異なる場合はテストを分けている

コメント