🐥

🧪 Pytest上級編:モックと例外処理、CI/CD連携まで

に公開

🚀 はじめに

この上級編では、Pytestをさらに強力に活用するための実践的テクニックを紹介します。具体的には、テストの対象となる関数や外部依存(API、データベース、ファイルシステムなど)をどのように扱うか、例外が発生する状況での適切なテスト方法、そしてGitHub Actionsなどの継続的インテグレーション(CI)ツールとどのように連携してテストの自動化を行うかを学びます。
これらの技術は、大規模開発やチーム開発において品質保証の要となるものであり、信頼性の高いコードベースの構築に不可欠です。特に外部要因に依存しない安定したテスト、再現性のあるテスト環境、迅速なフィードバックの提供が求められる現場では、今回紹介する上級テクニックが大いに役立ちます。

🎭 モック(Mock)の活用

外部API、データベースアクセス、ファイル読み書きなど、テスト中に実際に実行したくない処理を安全に置き換えるために「モック」を使います。Python標準ライブラリの unittest.mock や、よりPytestと親和性の高い pytest-mock を使うことで、柔軟かつ簡潔にモック処理を実現できます。

モックの基本:unittest.mock

from unittest.mock import Mock

def get_username(api):
    return api.get_user()['name']

def test_get_username():
    fake_api = Mock()
    fake_api.get_user.return_value = {'name': 'Taro'}
    assert get_username(fake_api) == 'Taro'

この例では、外部の api.get_user() の挙動を任意の値に置き換えてテストしています。これにより、実際のAPIアクセスをせずに関数のロジックのみを確認できます。

pytest-mock を使った例:

pip install pytest-mock
def get_status():
    import requests
    r = requests.get("https://example.com")
    return r.status_code

def test_status_code(mocker):
    mock_response = mocker.Mock()
    mock_response.status_code = 200
    mocker.patch("requests.get", return_value=mock_response)
    assert get_status() == 200

Pytestでモックを活用することで、テストの信頼性と実行スピードを両立できます。特にCI環境などで実行する際には、外部依存を完全に排除するのが理想です。

⚠️ 例外処理のテスト

エラーや例外が発生する状況をテストすることも重要です。Pytestの raises() を使えば、指定した例外が正しく発生したかを簡潔に検証できます。

基本例:

import pytest

def divide(x, y):
    if y == 0:
        raise ValueError("0で割ることはできません")
    return x / y

def test_divide_by_zero():
    with pytest.raises(ValueError, match="0で割る"):
        divide(10, 0)

ここでは、ゼロ除算時に ValueError が発生し、エラーメッセージも期待通りであることを確認しています。例外テストを正確に行うことで、エラーハンドリングの健全性を担保できます。

複数の例外タイプを扱うケース

def access_file(path):
    if not isinstance(path, str):
        raise TypeError("パスは文字列である必要があります")
    raise FileNotFoundError("ファイルが見つかりません")

def test_access_file_type():
    with pytest.raises(TypeError):
        access_file(123)

def test_access_file_not_found():
    with pytest.raises(FileNotFoundError):
        access_file("/invalid/path")

🤖 継続的インテグレーション(CI)との連携

CIを導入することで、コードが更新されるたびに自動でテストが実行されるようになります。これにより、人為的ミスを減らし、変更の影響を迅速に把握できます。

GitHub Actions の例:.github/workflows/test.yml

name: Run Pytest

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: 3.11
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run tests
        run: pytest --tb=short -v

これを設定すれば、GitHub上のプルリクエストごとにテストが走り、結果が可視化されます。自動化されたテストの導入は、プロジェクトの信頼性と開発スピードの両方を大幅に向上させます。

📦 より高度なテスト技法

以下はさらに発展的なテスト設計テクニックです:

  • パラメータの動的生成:CSVやJSONファイルなどから読み込んだデータを使って parametrize を自動構築
  • フィクスチャのスコープ管理:scope="session"scope="module" を用いて、セットアップの実行効率を最適化
  • 依存関係の注入(DI):引数ベースで依存性を渡し、テスト用の疑似オブジェクトやモックと差し替え可能にする
@pytest.fixture(scope="module")
def db_connection():
    conn = create_connection()
    yield conn
    conn.close()

これらを組み合わせることで、再利用性と柔軟性の高いテスト環境を構築できます。

🔺 まとめ

この上級編では、実践的かつ応用的なPytestの使い方を網羅的に紹介しました。特に次の点を学びました:

  • モックによる外部依存の排除とテストの独立性の確保
  • pytest.raises を用いた例外発生の精密なテスト
  • GitHub ActionsなどのCIツールを活用した継続的テストの実装
  • 動的パラメータ生成、スコープ制御、DIによる柔軟なテスト設計

Pytestの真価は、こうした高度な構成を組み合わせて、保守しやすく信頼性の高いテストスイートを作ることにあります。継続的なテスト自動化を通じて、チーム全体の開発体験を向上させましょう。


株式会社ONE WEDGE

【Serverlessで世の中をもっと楽しく】 ONE WEDGEはServerlessシステム開発を中核技術としてWeb系システム開発、AWS/GCPを利用した業務システム・サービス開発、PWAを用いたモバイル開発、Alexaスキル開発など、元気と技術力を武器にお客様に真摯に向き合う価値創造企業です。
https://onewedge.co.jp/

Discussion