TestcontainersをPythonから実行してみた

TestcontainersをPythonから実行してみた

コンテナを使い捨てのように実行できる Testcontainers というライブラリをPythonから実行してみました
Clock Icon2024.11.06

はじめに

こんにちは、アノテーションのなかたです。
今回は、コンテナを使い捨てのような使い方で実行できる Testcontainers というライブラリをPythonから実行してみました。
https://testcontainers.com/

軽量コンテナの使い捨てにより、データベースやブラウザなどモックが困難なミドルウェアのテストケースを解決しています。

やってみる

公式ドキュメントで挙げられているSQLAlchemyライブラリを使用して、PostgreSQLを呼び出すスクリプトを実行しています。

環境情報

  • macOS Intel Core i7
  • Rancher Desktop 1.16.0
  • dockerd(moby)

環境構築

パッケージマネージャとしてuvを使っているので、uvによるコマンド例になります。

testcontainersライブラリをインストールします。

uv の場合
uv add testcontainers --extra postgres
pip の場合
pip install testcontainers[postgres]

sqlalchemy, psycopg2 をインストールします。

uv add sqlalchemy psycopg2

DOCKER_HOSTという環境変数を利用しているコンテナランタイムに合わせて設定します。
私の環境ではRancher Desktopを利用しているので、.rdフォルダのパスを指定しました。

export DOCKER_HOST=unix:///Users/ユーザー名/.rd/docker.sock

サンプルスクリプトを実行してみます。

from testcontainers.postgres import PostgresContainer
import sqlalchemy

with PostgresContainer("postgres:16", driver=None) as postgres:
    psql_url = postgres.get_connection_url()
    engine = sqlalchemy.create_engine(psql_url)
    with engine.begin() as connection:
        version, = connection.execute(sqlalchemy.text("SELECT version()")).fetchone()

成功した際の出力は以下のようになりました。

% uv run python3 hello.py
warning: `VIRTUAL_ENV=/Users/xxx/Documents/Dev/vscode_python_coverage/.venv` does not match the project environment path `.venv` and will be ignored
using host unix:///Users/xxx/.rd/docker.sock
using host unix:///Users/xxx/.rd/docker.sock
Pulling image testcontainers/ryuk:0.8.1
Container started: 8e1d97c63097
Waiting for container <Container: 8e1d97c63097> with image testcontainers/ryuk:0.8.1 to be ready ...
Pulling image postgres:16
Container started: 2c6d781d3da5
Waiting for container <Container: 2c6d781d3da5> with image postgres:16 to be ready ...
Waiting for container <Container: 2c6d781d3da5> with image postgres:16 to be ready ...
psql_url = 'postgresql://test:test@localhost:32779/test'
engine = Engine(postgresql://test:***@localhost:32779/test)

発生したエラー

  • DOCKER_HOSTが空の状態で実行したところ、以下のエラーが発生しました。
エラーメッセージ
The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/xxx/Documents/Dev/testcontainers-python/hello.py", line 4, in <module>
    with PostgresContainer("postgres:16", driver=None) as postgres:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/testcontainers/postgres/__init__.py", line 59, in __init__
    super().__init__(image=image, **kwargs)
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/testcontainers/core/container.py", line 48, in __init__
    self._docker = DockerClient(**(docker_client_kw or {}))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/testcontainers/core/docker_client.py", line 67, in __init__
    self.client = docker.from_env(**kwargs)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/docker/client.py", line 94, in from_env
    return cls(
           ^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/docker/client.py", line 45, in __init__
    self.api = APIClient(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/docker/api/client.py", line 207, in __init__
    self._version = self._retrieve_server_version()
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/docker/api/client.py", line 230, in _retrieve_server_version
    raise DockerException(
docker.errors.DockerException: Error while fetching server API version: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))
  • testcontainers と sqlalchemy のみインストールした状態で実行したところ発生したエラーです。
    psycopg2 ライブラリをインストールする必要がありそうです。
エラーメッセージ
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File "<string>", line 2, in create_engine
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/util/deprecations.py", line 281, in warned
    return fn(*args, **kwargs)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/engine/create.py", line 599, in create_engine
    dbapi = dbapi_meth(**dbapi_args)
            ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py", line 690, in import_dbapi
    import psycopg2
ModuleNotFoundError: No module named 'psycopg2'
  • PostgresContainerdriver = Noneを渡さずに実行したところ、以下のエラーが発生しました
エラーメッセージ
Traceback (most recent call last):
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 146, in __init__
    self._dbapi_connection = engine.raw_connection()
                             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 3302, in raw_connection
    return self.pool.connect()
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/base.py", line 449, in connect
    return _ConnectionFairy._checkout(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/base.py", line 1263, in _checkout
    fairy = _ConnectionRecord.checkout(pool)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/base.py", line 712, in checkout
    rec = pool._do_get()
          ^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/impl.py", line 179, in _do_get
    with util.safe_reraise():
         ^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/util/langhelpers.py", line 146, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/impl.py", line 177, in _do_get
    return self._create_connection()
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/base.py", line 390, in _create_connection
    return _ConnectionRecord(self)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/base.py", line 674, in __init__
    self.__connect()
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/base.py", line 900, in __connect
    with util.safe_reraise():
         ^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/util/langhelpers.py", line 146, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/pool/base.py", line 896, in __connect
    self.dbapi_connection = connection = pool._invoke_creator(self)
                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/engine/create.py", line 643, in connect
    return dialect.connect(*cargs, **cparams)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 621, in connect
    return self.loaded_dbapi.connect(*cargs, **cparams)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Documents/Dev/testcontainers-python/.venv/lib/python3.12/site-packages/psycopg2/__init__.py", line 122, in connect
    conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.OperationalError: connection to server at "localhost" (127.0.0.1), port 32773 failed: Connection refused
        Is the server running on that host and accepting TCP/IP connections?
connection to server at "localhost" (::1), port 32773 failed: Connection refused
        Is the server running on that host and accepting TCP/IP connections?

(Background on this error at: https://sqlalche.me/e/20/e3q8)

おわりに

使い捨てコンテナのメリットが活かせそうなケースがあれば、利用しようと思います。

参考

https://dev.classmethod.jp/articles/rust-testcontainer/
https://qiita.com/KI1208/items/8e868173811e9861c9a9
https://testcontainers-python.readthedocs.io/en/latest/

アノテーション株式会社について

アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.