Docker Composeを使ってテストを動かすためのノウハウ
このエントリでは、
Docker Composeを使って、
テスト対象の起動からテスト実行までをまとめて実行したい時に、
必要になりそうなノウハウを取り上げます。
ここでは、次のような要件があると考えました。
- 開発環境構築用docker-compose.yamlはそのまま変更せず、テスト実行用のコンテナを追加したい
- テストの成否によってDocker Compose実行後の終了コードを変えたい
- 異常系テストのために一部コンテナを停止するなど、コンテナをテストコードから制御したい
- Docker Composeを用いたテストをGitHub Actionsで実行したい
テスト対象のdocker-compose.yamlを用意
まずはテスト対象となる環境を作ります。
ここでは、nginxが動いているだけのシンプルなdocker-compose.yamlを用意します。
ファイル構成
次のようなファイル構成にします。
- docker-compose.yaml
各ファイルの内容は次の通りです。
docker-compose.yaml
version: "3.9"
services:
app:
image: "nginx:latest"
ports:
- 8080:80
hostname: app
networks:
- app-network
networks:
app-network:
環境の起動
次のようにパラメータを指定して、環境を起動します。
docker compose up
http://localhost:8080/
にアクセスし、nginxが動いていることを確認します。
以降の説明で、この環境に対してテストを追加していきます。
開発環境構築用docker-compose.yamlはそのまま変更せず、テスト実行用のコンテナを追加したい
この要件に対しては、次のMultiple Compose filesを利用できます。
Share Compose configurations between files and projects | docker docs
https://docs.docker.com/compose/extends/
docker-compose.yaml に開発環境用の構成が書かれているとして、
docker-compose.test.yaml up にテスト用コンテナを追加・テストに必要な開発環境の設定の上書きを記載すれば、
次のように実行すれば、開発環境用構成にテスト用構成を追加してテストを実行できます。
docker compose -f docker-compose.yaml -f docker-compose.test.yaml up
対象環境が起動してからテストを実行したいので、healthyとdepends_onも指定しておきます。
healthcheck | Compose ファイル version 3 リファレンス
https://docs.docker.jp/compose/compose-file/compose-file-v3.html#healthcheck
depends_on | Compose ファイル version 3 リファレンス
https://docs.docker.jp/compose/compose-file/compose-file-v3.html#depends-on
ファイル構成
次のようなファイル構成にします。
- docker-compose.yaml
- docker-compose.test.yaml ★追加
- test
- Dockerfile ★追加
- .dockerignore ★追加
- requirements.txt ★追加
- tests
- test_study.py ★追加
各ファイルの内容は次の通りです。
docker-compose.test.yaml
version: "3.9"
services:
test:
build: test/
command: python -m pytest
depends_on:
app:
condition: service_healthy
networks:
- app-network
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
test/Dockerfile
FROM "python:3.10.11"
WORKDIR /test
COPY requirements.txt /test/
RUN pip install -r requirements.txt
COPY . /test
CMD ["python", "-m", "pytest"]
test/.dockerignore
venv
test/requirements.txt
pytest==7.3.1
requests==2.30.0
test/tests/test_study.py
import requests
def test_study():
r=requests.get("http://app/", timeout=(1,1))
assert "nginx" in r.text
テスト実行
次のようにパラメータを指定して、テストを実行します。
docker compose -f docker-compose.yaml -f docker-compose.test.yaml up --build
テスト結果が、以下のように表示されます。
multi-test-1 | ============================= test session starts ==============================
multi-test-1 | platform linux -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0
multi-test-1 | rootdir: /test
multi-test-1 | collected 1 item
multi-test-1 |
multi-app-1 | ***.***.***.*** - - [14/May/2023:18:20:08 +0000] "GET / HTTP/1.1" 200 615 "-" "python-requests/2.30.0" "-"
multi-test-1 | tests/test_study.py . [100%]
multi-test-1 |
multi-test-1 | ============================== 1 passed in 0.05s ===============================
この段階では、テスト実行後もdocker-composeが実行されたままになります。
その対応については、次の項目で説明します。
テストの成否によってDocker Compose実行後の終了コードを変えたい
この要件に対しては、docker compose upの--exit-code-from
が利用できます。--abort-on-container-exit
も利用しますが、exit-code-fromで暗黙的に指定されます。
docker-compose up | Docker-docs-ja
https://docs.docker.jp/compose/reference/up.html
テストの実行
次のようにパラメータを指定して、テストを実行します。
docker compose -f docker-compose.yaml -f docker-compose.test.yaml up --build --exit-code-from test
テスト成功時と失敗時で、次のように終了コードがかわります。
成功時
$ docker compose -f docker-compose.yaml -f docker-compose.test.yaml up --build --exit-code-from test
※省略※
multi-test-1 | ============================= test session starts ==============================
multi-test-1 | platform linux -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0
multi-test-1 | rootdir: /test
multi-test-1 | collected 1 item
multi-test-1 |
multi-app-1 | ***.***.***.*** - - [14/May/2023:18:24:13 +0000] "GET / HTTP/1.1" 200 615 "-" "python-requests/2.30.0" "-"
multi-test-1 | tests/test_study.py . [100%]
multi-test-1 |
multi-test-1 | ============================== 1 passed in 0.05s ===============================
※省略※
$ echo $?
0
失敗時
$ docker compose -f docker-compose.yaml -f docker-compose.test.yaml up --build --exit-code-from test
※省略※
multi-test-1 | ============================= test session starts ==============================
multi-test-1 | platform linux -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0
multi-test-1 | rootdir: /test
multi-test-1 | collected 1 item
multi-test-1 |
multi-app-1 | ***.***.***.*** - - [14/May/2023:18:25:12 +0000] "GET / HTTP/1.1" 200 615 "-" "python-requests/2.30.0" "-"
multi-test-1 | tests/test_study.py F [100%]
multi-test-1 |
multi-test-1 | =================================== FAILURES ===================================
multi-test-1 | __________________________________ test_study __________________________________
multi-test-1 |
multi-test-1 | def test_study():
multi-test-1 | r=requests.get("http://app/")
multi-test-1 | > assert "apache" in r.text
multi-test-1 | E assert 'apache' in ※省略※
multi-test-1 |
multi-test-1 | tests/test_study.py:5: AssertionError
multi-test-1 | =========================== short test summary info ============================
multi-test-1 | FAILED tests/test_study.py::test_study - assert 'apache' in '<!DOCTYPE html>\...
multi-test-1 | ============================== 1 failed in 0.06s ===============================
multi-test-1 exited with code 1
※省略※
$ echo $?
1
以上のように終了コードで成功判定できるので、CIにも組み込みやすくなります。
異常系テストのために一部コンテナを停止するなど、コンテナをテストコードから制御したい
この要件を満たすためには、
コンテナ内で動いているコードからDockerコマンドやAPIを利用出来るようにする必要があります。
これはDooD(Docker outside of Docker)と呼ばれている方法で実現する事ができます。
ホストの/var/run/docker.sock
をコンテナから参照すればOKです。
参考: dind(docker-in-docker)とdood(docker-outside-of-docker)でコンテナを料理する | qiita https://qiita.com/t_katsumura/items/d5be6afadd6ec6545a9d
また、コンテナを操作するためには、対象のコンテナを判別する必要があります。
docker-composeで指定したサービス名からコンテナIDを特定するには、
dockerコンテナのラベルを使います。
以下の2つのラベルを利用します。
- com.docker.compose.service: サービス名
- com.docker.compose.project: プロジェクト名
Services top-level element | docker-docs
https://docs.docker.com/compose/compose-file/05-services/
ファイル構成
次のようなファイル構成にします。
- docker-compose.yaml
- docker-compose.test.yaml ★更新
- test
- Dockerfile
- .dockerignore
- requirements.txt ★更新
- tests
- test_study.py ★更新
各ファイルの内容は次の通りです。
docker-compose.test.yaml
version: "3.9"
services:
test:
build: test/
command: python -m pytest
depends_on:
app:
condition: service_healthy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- app-network
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
test/requirements.txt
pytest==7.3.1
requests==2.30.0
docker==6.1.2
test/tests/test_study.py
import pytest
import requests
from requests.exceptions import Timeout
import docker
def test_study():
client: docker.DockerClient = docker.from_env()
project="※プロジェクト名に置き換え※"
service="app"
containers = [
c.id
for c
in client.containers.list(filters={
'label': [f"com.docker.compose.project={project}", f"com.docker.compose.service={service}"],
'status': 'running'
})
]
# pause containers
for c in containers:
client.api.pause(c)
with pytest.raises(Timeout) as e:
r=requests.get("http://app/", timeout=(1,1))
# unpause containers
for c in containers:
client.api.unpause(c)
テストの実行
テストを実行すると、次のように結果が出力されます。
$ docker compose -f docker-compose.yaml -f docker-compose.test.yaml up --build --exit-code-from test
※省略※
multi-test-1 | ============================= test session starts ==============================
multi-test-1 | platform linux -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0
multi-test-1 | rootdir: /test
multi-test-1 | collected 1 item
multi-test-1 |
multi-app-1 | ***.***.***.*** - - [14/May/2023:19:19:43 +0000] "GET / HTTP/1.1" 200 0 "-" "python-requests/2.30.0" "-"
multi-test-1 | tests/test_study.py . [100%]
multi-test-1 |
multi-test-1 | ============================== 1 passed in 1.12s ===============================
multi-test-1 exited with code 0
※省略※
Docker Composeを用いたテストをGitHub Actionsで実行したい (2023.05.21追記)
最後に、これらをGitHub Actionsから実行します。
ここまでの手順でもろもろの準備はできているので、 あとは、GitHub Actionsのワークフローでdocker composeを実行するだけです。
ファイル構成
次のようなファイル構成にします。
- docker-compose.yaml
- docker-compose.test.yaml
- test
- Dockerfile
- .dockerignore
- requirements.txt
- tests
- test_study.py
- .github
- workflows
- e2etest.yaml
- workflows
各ファイルの内容は次の通りです。
.github/workflows/e2etest.yaml
name: e2etest
on: [push]
jobs:
unit_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: test
run: docker compose -f docker-compose.yaml -f docker-compose.test.yaml up --build --exit-code-from test
以上。