2023年6月29日木曜日

SQLAlchemyでautomap_baseを使った場合にpytestでmonkeypatchする方法

SQLAlchemyでautomap_baseを使った場合にpytestでmonkeypatchする方法

概要

前回 automap 機能を使ってみました
今回はそのコードに対してデータベースに接続しないで pytest を実行できるように monkeypatch を当ててみました

環境

  • macOS 13.4.1
  • Python 3.11.3
  • sqlalchemy 2.0.17
  • PyMySQL 1.1.0

コード

テストするコードは以下の main メソッドをテストします
実際に mysql が動作していなくてもテストが通るようにします

  • vim app.py
import sqlalchemy.ext.automap
from sqlalchemy import create_engine

# from sqlalchemy.ext.automap import automap_base # monkeypatch が当てられないパターン、必ずコード側とテストコード側では同じ import 方式にする
from sqlalchemy.orm import Session


def main():
    # automap を使用するための準備
    Base = sqlalchemy.ext.automap.automap_base()
    # Base = automap_base() # monkeypatch が当てられないパターン
    # エンジンの作成
    engine = create_engine("mysql+pymysql://root@localhost/test?charset=utf8mb4")
    # テーブル定義の読み込み
    Base.prepare(autoload_with=engine)
    # user テーブルから user モデルの抽出
    User = Base.classes.user
    # CRUD 用セッションの作成
    session = Session(engine)
    # ex) データ全件取得
    users = session.query(User).all()
    for user in users:
        print(user.name)

テストコード

conftest で monkeypatch を当てまくります
ポイントは

  • Base.prepare で接続しに行かせない
  • Session で好きな情報を返却させる

になります

  • vim test/conftest.py
import pytest
import sqlalchemy.ext.automap
from sqlalchemy.orm import Session


# automap_base に関するモック、これでデータベースへの接続を回避する
class DummyUser:
    def __init__(self) -> None:
        self.name = "hoge"


class DummyClasses:
    def __init__(self) -> None:
        self.user = DummyUser()


class DummyBase:
    def __init__(self) -> None:
        self.classes = DummyClasses()

    def prepare(self, autoload_with: bool = True):
        pass


def dummy_automap_base(declarative_base=None, **kwargs):
    return DummyBase()


# session.query 対するモック、データベースからの返却される値はこっちでコントロールする
class DummyQuery:
    def all(self):
        return []


def dummy_query(*args, **kwargs):
    return DummyQuery()


@pytest.fixture(autouse=True)
def mock_automap_base(monkeypatch):
    monkeypatch.setattr(sqlalchemy.ext.automap, "automap_base", dummy_automap_base)
    monkeypatch.setattr(Session, "query", dummy_query)
  • vim test/test_app.py
from app import main


class TestApp:
    def test_app(self):
        main()

動作確認

  • pipenv run pytest -s .

でデータベースが停止していてもテストが通ることを確認します

最後に

かなり無理やりな感じはしますが automap を使ったコードにデータベースに接続しないように monkeypatch を当ててみました
この方式の場合せっかくモデルを自動生成できるのにテスト側ではモデルを手動で生成しないといけないのが辛いです
なので automap を使ったユニットテストを書く場合には データベースが必須という状況でもいいのかもしれません

0 件のコメント:

コメントを投稿