1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FastAPI × SQLAlchemy × Alembic × pytest で安全にテスト用 DB を使う方法

Posted at

はじめに

FastAPIでDBが絡むテストをしたい場合、モックを使用せずに実際のデータベースを操作することがあります。その際には、本番DBと分離したテスト用DBが必要です。
この備忘録では、テスト用DBの作成、マイグレーション、pytest設定などをまとめています。

テスト用DBの作成

DBが絡むテストをしたい場合は基本的にはプロダクト用とテスト用に使うDBは分ける
そのための手段としてテストDB用のDockerコンテナを作成

test-db:
    image: postgres:15-alpine
    container_name: myapp-test-db
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpassword
    networks:
      - app-network
    ports:
      - "5433:5432"  # ホストからアクセスする場合はポート分ける
    volumes:
      - test_db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U testuser -d testdb"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

基本的なフロー

  1. pytest実行時にDBの向き先をテストDBコンテナに向ける
  2. マイグレーションの実行
  3. マイグレーションをベースに戻す

テストケース毎にトランザクションを張ってdb操作が他のケースやスイートに影響しないようにする

pytestの設定

conftest.pyを作成して以下のfixtureと設定の追加

テスト用DBのエンジンとセッションの作成

TEST_DATABASE_URL = os.getenv(
    "TEST_DATABASE_URL",
    "postgresql://testuser:testpassword@test-db:5432/testdb"
)

engine = create_engine(TEST_DATABASE_URL)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Alembicマイグレーションをセッション単位で適用

@pytest.fixture(scope="session")
def apply_migrations():
    """テストDBにマイグレーションを適用"""
    os.environ["DATABASE_URL"] = TEST_DATABASE_URL
    alembic_path = os.path.join(os.path.dirname(__file__), "../src/alembic.ini")
    alembic_cfg = Config(alembic_path)
    alembic_cfg.set_main_option("sqlalchemy.url", TEST_DATABASE_URL)

    # 既存のテーブルをクリーンアップしてからマイグレーション実行
    command.downgrade(alembic_cfg, "base")
    command.upgrade(alembic_cfg, "head")
    yield
    command.downgrade(alembic_cfg, "base")

DBセッション

@pytest.fixture
def db_session(apply_migrations):
    """テスト用の DB セッションを提供"""
    connection = engine.connect()
    transaction = connection.begin()
    session = TestingSessionLocal(bind=connection)

    try:
        yield session
    finally:
        session.close()
        transaction.rollback()
        connection.close()

APIのDBの依存をテスト用に変更

@pytest.fixture
def client(db_session):
    print("テスト用セッションs")
    """TestClient を提供し、get_db をテスト用セッションに差し替える"""
    def override_get_db():
        try:
            yield db_session
        finally:
            pass  # rollback は db_session fixture で処理済み

    # 上書き
    app.dependency_overrides[get_db] = override_get_db

    from fastapi.testclient import TestClient
    with TestClient(app) as c:
        yield c

テストコード例

import pytest
from fastapi.testclient import TestClient


def test_create_todo(client: TestClient):
    """Todoデータ登録のテスト"""
    todo_data = {
        "title": "テストタスク",
        "description": "これはテスト用のタスクです",
        "priority": "high"
    }
    
    response = client.post("/api/v1/todos", json=todo_data)
    
    assert response.status_code == 200
    data = response.json()
    assert data["title"] == todo_data["title"]
    assert data["description"] == todo_data["description"]
    assert data["priority"] == todo_data["priority"]
    assert data["completed"] == False
    assert "id" in data
    assert "created_at" in data

環境変数の扱い方

pytest などでテスト DB を利用する場合、os.environ を書き換える方法があります。

import os

os.environ["DATABASE_URL"] = TEST_DATABASE_URL

環境変数はプロセス毎なので
pytest と uvicorn は別プロセスなので、pytest 内で書き換えても本番の uvicorn には影響しません。
(同一プロセス内で書き換えると、本番コードがテスト DB に接続してしまう)

補足・注意点

  • マイグレーションの戻し方
    command.downgrade(alembic_cfg, "base") をテスト終了後に実行すると、テスト用 DB のテーブルは削除されます。
    テスト中にテーブルが必要な場合は、yield の後に実行するようにしましょう。

  • 環境変数の書き換え
    os.environ["DATABASE_URL"] = TEST_DATABASE_URL は同一プロセス内で書き換えると本番 DB に影響します。
    pytest と uvicorn が別プロセスなら安全に使えます。

  • トランザクション管理
    テストケースごとにトランザクションを張ることで、DB 操作が他のテストに影響せず、安全に並行テスト可能です。

  • Docker コンテナの分離
    テスト DB と本番 DB を完全に分離することで、誤って本番 DB を操作するリスクを減らせます。

  • Alembic 設定
    alembic.ini 内の script_location は必須です。
    本番やテストでマイグレーションを実行する場合、正しいパスを指定しておく必要があります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?