Veltioblog

Testcontainers と pytest で Python のテストを書く

Testcontainers と pytest で Python のテストを書く例を示します(sqlc と factoryboy を添えて)。


Testcontainers は テスト時に Docker コンテナを作成し、テストが終了したら削除してくれるライブラリです。 また、Python、Go、Rust、TS/JS(Node.js)、Elixir など幅広い言語に対応しています。

Testcontainers がどんな問題を解決するのかについてはこちらを読んでみてください。

What is Testcontainers, and why should you use it?

この記事では、Python 使ったシンプルな Web アプリケーションを作成し、そのテストに Testcontainers を利用します。

この記事で紹介するコード全体はこちら: https://github.com/zztkm/code-for-blog/tree/main/2023/pytest_with_testcontainers

Table of contents

今回紹介するアプリケーションの技術スタック

Python で Web アプリケーションを作成するのに、FastAPI を利用し、DB には MySQL を利用します。

実装

DB イメージ作成

はじめに、テーブル定義を記述した SQL ファイルを用意します。

create table `menus` (
  `id` int not null auto_increment,
  `code` varchar(255) not null,
  `name` varchar(255) not null,
  `price` int not null,
  `description` varchar(255) not null,
  primary key (`id`),
  unique key `menus_code_unique` (`code`)
) engine=InnoDB default charset=utf8mb4;

そのテーブル定義を事前に適用した MySQL イメージを作成します。

FROM mysql:8.2

COPY schema.sql /docker-entrypoint-initdb.d/01_create_table.sql

今回の例では次のようにビルドしておきます docker build . -t udon-db:latest

pytest の設定

テスト時に利用する共通 fixture を conftest.py に定義します。

mysql fixture を定義し、MySQL のコンテナを起動します(scope="session" にしておくとテストセッション内で一度だけ実行され、以降はキャッシュされます)。 あとは、mysql インスタンスの get_connection_url() メソッドから DB 接続用 URL を取得し、DB 接続クライアントに渡すことで、テスト用 MySQL コンテナに接続できるようになります。

pytest fixture scopes の詳細はこちらを参照してください。 -> https://docs.pytest.org/en/latest/how-to/fixtures.html#fixture-scopes

@pytest.fixture(scope="session")
def mysql() -> MySqlContainer:
    """MySQLコンテナのフィクスチャです。

    engineから呼び出されるため、通常は他のテストから呼び出されません。

    Returns
    -------
        MySqlContainer:
    """
    # 事前に作成した MySQL イメージを利用
    mysql = MySqlContainer(
        image="udon-db:latest",
    )
    mysql.start()
    return mysql
    # NOTE: pytestが正常に終了したら、自動的に stop が呼ばれる


@pytest.fixture(scope="session")
def engine(mysql: MySqlContainer) -> Engine:
    """MySQLコンテナから SQLAlchemy の Engine を作成します。
    connectionから呼び出されるため、通常は他のテストから呼び出されません。

    Args:
    ----
        mysql (MySqlContainer): MySQLコンテナのfixture

    Returns:
    -------
        Engine: SQLAlchemy の Engine
    """
    return create_engine(mysql.get_connection_url())

完全な例はこちら -> conftest.py

まとめ

Docker Compose を使って DB などの依存関係を構築し、統合テストを行うことも可能ですが、Testcontainers を使うと、テストコード内で依存関係を定義できるし、コンテナのお掃除などを自分でする必要がないので、気軽にコンテナを使ったテストができて便利です。

実際に開発で運用していくとしたら、Testcontainers を使ってユニットテストしつつ、ローカルサーバーを立ち上げて手動テストするのに Docker Compose を使うのが良さげだなと考えてます。

今回は Python を例にしましたが、他の言語でも同じように使えるので、ぜひ試してみてください。