libfaketimeでシステム時間を変化させたテストする
Dockerでコンテナ化されたWebアプリケーションに対して、
システム時間を変化させたテストを実行したかったので、手順をまとめました。
ここでは、libfaketimeというものを使います。
libfaketime | GitHub
https://github.com/wolfcw/libfaketime
対象のWebアプリケーション
テスト対象のWebアプリは、次の仕様とします。
- 土曜日・日曜日の時: Hello, Holiday! と表示
- それ以外: Hello, Working! と表示
以降に、構成と実行の流れを示します。
対象Webアプリの構成
ディレクトリ構成と各ファイルは次の通り。
ディレクトリ構成
- docker-compose.yaml
- app
- main.py
- requirements.txt
- Dockerfile
各ファイルの内容
app/main.py
import datetime
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
if datetime.datetime.now().weekday() in [5, 6]:
return "<p>Hello, Holiday!</p>"
return f"<p>Hello, Working!</p>"
if __name__ == "__main__":
app.run(host="0.0.0.0")
app/requirements.txt
Flask==2.2.3
app/Dockerfile
FROM python:3.10.10
WORKDIR /app
COPY requirements.txt /app
RUN pip install -r requirements.txt
COPY . /app
CMD ["python", "main.py"]
docker-compose.yaml
services:
app:
build:
context: app
ports:
- 5000:5000
対象Webアプリの実行
Webアプリを起動する。
docker compose up --build
Webアプリにアクセスする。
$ curl http://localhost:5000/
<p>Hello, Working!</p>
※土日であれば、Hello, Holiday!が返却される。
対象のWebアプリにLibfaketimeを追加する
テスト対象のWebアプリにlibfaketimeを追加します。
対象のWebアプリのイメージやdocker-composeを変更したくないので、
ライブラリのbuild用のコンテナを追加します。
以降に、追加するファイルと実行の流れを示します。
構成に追加するファイル
追加するファイルは次の通り。
ディレクトリ構成
- docker-compose.yaml
- docker-compose.libfaketime.yaml ★追加
- app
- main.py
- requirements.txt
- Dockerfile
- libfaketime
- Dockerfile ★追加
各ファイルの内容
docker-compose.libfaketime.yaml
services:
app:
depends_on:
- libfaketime-build
environment:
- LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1
volumes:
- libfaketime:/usr/local/lib/faketime
libfaketime-build:
build:
context: libfaketime
command: cp -f /usr/local/lib/faketime/libfaketime.so.1 /mnt
volumes:
- libfaketime:/mnt
volumes:
libfaketime:
libfaketime/Dockerfile
FROM python:3.10.10
WORKDIR /
RUN apt-get update \
&& apt-get install -y wget build-essential \
&& wget https://github.com/wolfcw/libfaketime/archive/refs/tags/v0.9.10.tar.gz \
&& tar zxf v0.9.10.tar.gz \
&& cd libfaketime-0.9.10/ \
&& make install \
&& cd ../ \
&& rm -rf libfaketime-0.9.10 \
&& rm v0.9.10.tar.gz
VOLUME /usr/local/lib/faketime
libfaketimeを適用してアプリを実行する
libfaketimeをロードしてWebアプリを起動する。
docker compose -f docker-compose.yaml -f docker-compose.libfaketime.yaml up -d --build
Webアプリのコンテナを停止、FAKETIMEを指定して起動する。(平日の場合)
docker compose -f docker-compose.yaml -f docker-compose.libfaketime.yaml stop app
docker compose -f docker-compose.yaml -f docker-compose.libfaketime.yaml run -d -e "FAKETIME=2023-03-06 00:00:00" --service-ports app
$ curl http://localhost:5000/
<p>Hello, Working!</p>
Webアプリのコンテナを停止、FAKETIMEを指定して起動する。(土日の場合)
docker compose -f docker-compose.yaml -f docker-compose.libfaketime.yaml stop app
docker compose -f docker-compose.yaml -f docker-compose.libfaketime.yaml run -d -e "FAKETIME=2023-03-12 00:00:00" --service-ports app
$ curl http://localhost:5000/
<p>Hello, Holiday!</p>
コンテナを終了する。
docker compose -f docker-compose.yaml -f docker-compose.libfaketime.yaml down
コンテナの停止・起動をPythonのスクリプトでコントロールする
手動でコンテナの上げ下げでもテストできるのですが、
自動テスト用にPythonでコンテナの制御もしてみます。
テストコードの実装
テストコードは次の通り。
ディレクトリ構成
- docker-compose.yaml
- docker-compose.libfaketime.yaml
- app
- main.py
- requirements.txt
- Dockerfile
- libfaketime
- Dockerfile
- test.py ★追加
- requirements.txt ★追加
各ファイルの内容
test.py
import time
import docker
import requests
import pytest
@pytest.fixture
def faketime_app(request):
service, faketime = request.param
client: docker.DockerClient = docker.from_env()
# build arguments for new container
container_ids = [
c.id
for c in client.containers.list(filters={'label': f"com.docker.compose.service={service}", 'status': 'running'})
]
if len(container_ids) == 0:
pytest.fail(f"{service} container is not running")
container = client.containers.get(container_ids[0])
environments = [
e
for e in container.attrs.get("Config", {}).get("Env")
if e.split("=")[0] != "FAKETIME"
] + [
f"FAKETIME={faketime}"
]
create_args = {
'image': container.attrs.get("Image"),
'command': container.attrs.get("Config", {}).get("Cmd"),
'ports': [pt.split('/')[0] for pt in container.attrs.get("HostConfig", {}).get("PortBindings", {})],
'environment': environments,
'volumes': container.attrs.get("Config", {}).get("Volumes"),
'host_config': container.attrs.get("HostConfig"),
'labels': container.attrs.get("Config", {}).get("Labels"),
}
# stop container
client.api.stop(container.id)
client.api.wait(container.id)
# create and start faketime container
new_container = client.api.create_container(**create_args)
client.api.start(new_container.get("Id"))
time.sleep(1)
yield
# stop faketime container
client.api.stop(new_container.get("Id"))
client.api.wait(new_container.get("Id"))
client.api.remove_container(new_container.get("Id"))
# start original container
client.api.start(container.id)
@pytest.mark.parametrize(
"faketime_app, expect_resp",
[
(("app", "2023-03-06 00:00:00"), "Working"),
(("app", "2023-03-12 00:00:00"), "Holiday"),
],
indirect=["faketime_app"]
)
def test_app(faketime_app, expect_resp):
resp = requests.get('http://localhost:5000/', timeout=3)
assert expect_resp in resp.text
requirements.txt
docker==6.0.1
pytest==7.2.2
requests==2.28.2
venv仮想環境のセットアップ
venv仮想環境を次のように準備する。
python -m venv venv
. venv/bin/activate
pip install -r requirements.txt
テスト実行
コンテナを起動する。
docker compose -f docker-compose.yaml -f docker-compose.libfaketime.yaml up -d --build
テストを実行する。
. venv/bin/activate
python -m pytest test.py
コンテナを停止する。
docker compose -f docker-compose.yaml -f docker-compose.libfaketime.yaml down
以上。