Flask のテスト戦略と pytest の活用

Flask アプリケーションのテストは pytest を使うと効率的に書ける。テストクライアント、フィクスチャ、モックを活用したテスト戦略を解説する。

基本的なテスト設定

# tests/conftest.py
import pytest
from app import create_app
from app.extensions import db

@pytest.fixture
def app():
    app = create_app('testing')
    with app.app_context():
        db.create_all()
        yield app
        db.drop_all()

@pytest.fixture
def client(app):
    return app.test_client()

@pytest.fixture
def runner(app):
    return app.test_cli_runner()

テストクライアントの使い方

# tests/test_views.py
def test_index(client):
    response = client.get('/')
    assert response.status_code == 200
    assert b'Welcome' in response.data

def test_login(client):
    response = client.post('/login', data={
        'username': 'alice',
        'password': 'secret'
    })
    assert response.status_code == 302  # リダイレクト

セッションを使ったテスト

def test_authenticated_access(client):
    with client.session_transaction() as sess:
        sess['user_id'] = 1
    
    response = client.get('/dashboard')
    assert response.status_code == 200

JSON API のテスト

def test_api_create_user(client):
    response = client.post('/api/users',
        json={'name': 'Alice', 'email': 'alice@example.com'},
        content_type='application/json'
    )
    assert response.status_code == 201
    data = response.get_json()
    assert data['name'] == 'Alice'

データベースを使ったテスト

# tests/conftest.py
@pytest.fixture
def sample_user(app):
    from app.models import User
    user = User(username='testuser', email='test@example.com')
    user.set_password('password')
    db.session.add(user)
    db.session.commit()
    return user

# tests/test_user.py
def test_user_login(client, sample_user):
    response = client.post('/login', data={
        'username': 'testuser',
        'password': 'password'
    })
    assert response.status_code == 302

モックの活用

from unittest.mock import patch

def test_send_email(client, sample_user):
    with patch('app.services.email_service.send_email') as mock_send:
        mock_send.return_value = True
        response = client.post('/reset-password', data={
            'email': 'test@example.com'
        })
        assert response.status_code == 200
        mock_send.assert_called_once()

カバレッジの計測

pip install pytest-cov
pytest --cov=app tests/

テストの構成

tests/
├── conftest.py           # 共通フィクスチャ
├── test_views.py         # ビューのテスト
├── test_api.py           # API のテスト
├── test_models.py        # モデルのテスト
├── test_services.py      # サービス層のテスト
└── test_integration.py   # 統合テスト

ファクトリを使ったテストデータ

# tests/factories.py
from app.models import User

class UserFactory:
    _counter = 0
    
    @classmethod
    def create(cls, **kwargs):
        cls._counter += 1
        defaults = {
            'username': f'user{cls._counter}',
            'email': f'user{cls._counter}@example.com'
        }
        defaults.update(kwargs)
        user = User(**defaults)
        db.session.add(user)
        db.session.commit()
        return user

pytest とフィクスチャを活用すれば、テストコードの重複を減らしつつ、包括的なテストを実現できる。