(32) pytest入門:AgenticAIシステムのテスト基盤構築

(32) pytest入門:AgenticAIシステムのテスト基盤構築

こんにちは!今回は、AgenticAIシステムのテストにおいて欠かせないツール「pytest」について、基本的な使い方から実践的な活用法まで、一緒に学んでいきましょう。ReadyTensorのWeek 9 Lesson 2aでは、なぜpytestがエージェントシステムに最適なのか、そして実際にどのように使うかを詳しく解説しています。

なぜAgenticAIにpytestなのか?

Pythonでコードを書いて「これがずっと動作し続けてほしい」と思ったことがある方なら、pytestの存在価値を理解する準備はできています。pytestは、従来のテストフレームワークとは根本的に異なるアプローチを取っています。

従来のテストフレームワークでは、 2 + 2 == 4をチェックするだけでも、クラスやボイラープレートコードを大量に書く必要がありました。しかし、pytestは正反対のアプローチを取ります。普通の関数を書き、普通のassert文を使う、それだけです。これでテストが完成します。

このシンプルさこそが、AgenticAIシステムでpytestを使う最大の理由です。エージェントシステムが成長するにつれて、エージェントノード、ツール、レトリーバー、フォーマッター、フォールバックロジックなど、多くのコンポーネントが追加されます。これらすべてをテストする必要があるのに、複雑なテストフレームワークでは開発の足枷になってしまいます。

pytestの基本的な使い方

インストールと設定

pytestのインストールは非常に簡単です:

pip install pytest

ただし、実際のプロジェクトでは、テスト依存関係を明確に管理することが重要です。 requirements-test.txtファイルを作成して、テストに必要なパッケージをリストアップしましょう:

# requirements-test.txt
pytest>=8.4

これにより、本番システムとテストシステムの依存関係を分離できます。本番システムはテストツールを必要としないため、この分離により軽量なコンテナでの実行が可能になります。

最初のテストを書く

基本的な計算機の例から始めてみましょう。まず、テスト対象のコードを作成します:

# calculator.py
def add(a, b):
    """Add two numbers and return the result."""
    return a + b

def multiply(a, b):
    """Multiply two numbers and return the result."""
    return a * b

def divide(a, b):
    """Divide two numbers and return the result."""
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

次に、対応するテストファイルを作成します:

# test_calculator.py
import pytest
from calculator import add, multiply, divide

def test_add_positive_numbers():
    result = add(2, 3)
    assert result == 5

def test_add_negative_numbers():
    result = add(-1, -1)
    assert result == -2

def test_multiply_basic():
    result = multiply(4, 3)
    assert result == 12

def test_divide_basic():
    result = divide(10, 2)
    assert result == 5.0

def test_divide_by_zero():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(10, 0)

ここで注目すべきポイントは:

  • 各テスト関数はtest_で始まる
  • 結果の検証に普通のassert文を使用
  • 例外の発生を期待する場合はpytest.raisesを使用
  • テストメッセージの内容も検証可能

テストの実行と結果の確認

テストを実行するには、プロジェクトディレクトリで以下のコマンドを実行します:

pytest

pytestは自動的に test_<em>.pyまたは</em><em>test.pyにマッチするファイルを検索し、test</em>で始まるすべての関数を実行します。

実行結果は以下のような形式で表示されます:

===================================== test session starts =========================================
collected 5 items

tests\test_calculator.py .....                                                               [100%]

====================================== 5 passed in 0.03s ==========================================

各ドットは成功したテストを表し、失敗した場合は Fが表示されます。この視覚的なフィードバックにより、テストの状況を即座に把握できます。

より複雑なテストの作成

クラスベースのテスト

実際のプロジェクトでは、より複雑なオブジェクトをテストする必要があります。銀行口座のクラスを例に見てみましょう:

# bank_account.py
class BankAccount:
    def __init__(self, initial_balance=0):
        if initial_balance < 0:
            raise ValueError("Initial balance cannot be negative")
        self._balance = initial_balance

    @property
    def balance(self):
        return self._balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self._balance += amount
        return self._balance

    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive")
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        self._balance -= amount
        return self._balance

対応するテストクラスは以下のようになります:

# test_bank_account.py
import pytest
from bank_account import BankAccount

class TestBankAccount:
    def test_initial_balance_default(self):
        """Test account creation with default balance."""
        account = BankAccount()
        assert account.balance == 0

    def test_deposit_positive_amount(self):
        """Test depositing positive amount."""
        account = BankAccount(100)
        new_balance = account.deposit(50)
        assert new_balance == 150
        assert account.balance == 150

    def test_withdraw_insufficient_funds(self):
        """Test that withdrawing more than balance raises error."""
        account = BankAccount(50)
        with pytest.raises(ValueError, match="Insufficient funds"):
            account.withdraw(100)

pytest の高度な機能

フィクスチャーによるテストセットアップ

複数のテストで同じセットアップが必要な場合、フィクスチャーを使用して重複を避けることができます:

import pytest
from bank_account import BankAccount

@pytest.fixture
def sample_account():
    return BankAccount(100)

@pytest.fixture
def two_accounts():
    return BankAccount(100), BankAccount(50)

def test_deposit_with_fixture(sample_account):
    sample_account.deposit(50)
    assert sample_account.balance == 150

def test_transfer_with_fixture(two_accounts):
    account1, account2 = two_accounts
    account1.transfer(25, account2)
    assert account1.balance == 75
    assert account2.balance == 75

パラメータ化テスト

同じテストロジックを複数の入力で実行する場合、パラメータ化テストが有効です:

@pytest.mark.parametrize("a,b,expected", [
    (1, 1, 2),
    (2, 3, 5),
    (-1, 1, 0),
    (0, 0, 0),
])
def test_add_parametrized(a, b, expected):
    assert add(a, b) == expected

pytestはこのテストを4回実行し、各入力の組み合わせを個別に検証します。

テストマーカーによる分類

テストにマーカーを付けることで、特定のテストグループを選択的に実行できます:

@pytest.mark.slow
def test_complex_operation():
    1. 時間のかかる処理をシミュレート
    import time; time.sleep(2)
    assert True

@pytest.mark.integration
def test_external_api_call():
    assert True

@pytest.mark.unit
def test_simple_math():
    assert 2 + 2 == 4

実行時に特定のマーカーを指定できます:

# ユニットテストのみ実行
pytest -m "unit"

1. 遅いテストをスキップ
pytest -m "not slow"

1. 統合テストを実行
pytest -m "integration"

効率的なテスト実行

便利なコマンドラインオプション

pytestには、開発効率を向上させる多くのオプションがあります:

# より詳細なテスト出力を表示
pytest -v

1. 最初の失敗後に停止
pytest -x

1. 最も遅い10個のテストを表示
pytest --durations=10

1. 前回失敗したテストのみ実行
pytest --lf

1. 失敗したテストを最初に実行
pytest --ff

1. 並列でテストを実行(pytest-xdistが必要)
pytest -n auto

1. コードカバレッジレポートを生成(pytest-covが必要)
pytest --cov=code --cov-report=html

設定ファイルの活用

プロジェクトルートに pytest.iniファイルを作成することで、デフォルトの設定を定義できます:

# pytest.ini
[pytest]
addopts = --verbose --tb=short
markers =
    slow: mark tests as slow (use with -m "not slow")
    integration: mark integration tests
    unit: mark unit tests
python_files = test_*.py

この設定により、毎回同じオプションを入力する必要がなくなり、チーム全体で一貫したテスト実行環境を構築できます。

テスト組織化のベストプラクティス

ディレクトリ構造

テストファイルは、メインコードの構造をミラーリングする形で組織化することが推奨されます:

my_project/
├── code/
│   ├── data_processing/
│   │   └── loader.py
│   └── agents/
│       └── tagger.py
└── tests/
    ├── data_processing/
    │   └── test_loader.py
    └── agents/
        └── test_tagger.py

効果的なテスト名

テスト名は、何をテストしているか、どのような条件下でテストしているかを明確に示すべきです:

# 良い例
def test_withdraw_insufficient_funds():
    pass

def test_deposit_negative_amount_raises_error():
    pass

1. 悪い例
def test_case_3():
    pass

def test_error():
    pass

AgenticAIシステムでのpytest活用

AgenticAIシステムでは、従来のソフトウェアテストに加えて、非決定的な出力を生成するLLMコンポーネントのテストも必要になります。pytestの柔軟性により、これらの複雑な要求にも対応できます。

次のレッスンでは、実際のエージェントコンポーネント(エージェントノード、ツールラッパー、プロンプトテンプレート、メモリマネージャー)のテスト方法を学び、変動性があるAIシステムでも信頼性を構築する方法を探求していきます。

pytestは、シンプルでありながら強力なテストフレームワークとして、AgenticAIシステムの品質保証における重要な基盤となります。基本的な使い方を習得することで、より複雑なAIシステムのテストにも対応できるようになるでしょう。

参考リンク

コメント

タイトルとURLをコピーしました