TestcontainersをPythonから実行してみた
はじめに
こんにちは、アノテーションのなかたです。
今回は、コンテナを使い捨てのような使い方で実行できる Testcontainers というライブラリをPythonから実行してみました。
軽量コンテナの使い捨てにより、データベースやブラウザなどモックが困難なミドルウェアのテストケースを解決しています。
やってみる
公式ドキュメントで挙げられているSQLAlchemyライブラリを使用して、PostgreSQLを呼び出すスクリプトを実行しています。
環境情報
- macOS Intel Core i7
- Rancher Desktop 1.16.0
- dockerd(moby)
環境構築
パッケージマネージャとしてuvを使っているので、uvによるコマンド例になります。
testcontainers
ライブラリをインストールします。
uv add testcontainers --extra postgres
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'
PostgresContainer
にdriver = 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)
おわりに
使い捨てコンテナのメリットが活かせそうなケースがあれば、利用しようと思います。
参考
アノテーション株式会社について
アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。