テストでlocalstack利用時に起動時のPort待受が完了するまで待機するようにしてみた
AWSへのアクセスが必要なコードのテスト用途にlocalstackを利用していると、幾つかの課題が出てきます。
- localstackを導入していない環境ではエラーになる
- localstackの起動が完了する前にテストを実行してエラーになる
- Actions等のCI環境で起動が完了する前にテストが走ってエラーになる
他にも懸念事項はあるかもしれませんが、今回は上記3点についてとった対策について書いてみます。
テストコード内でlocalstackの起動判定を入れる
localstackが起動していない場合にエラー扱いとしないために、HTTPリクエストを受付なければskipさせる方法でやってみます。
import requests try: res = requests.get('http://localhost:4566', timeout=5) return res.json()['status'] == 'running' except Exception: return False
こんな感じで問題ありません。ただ、localstackを使うテスト毎にこれを書き入れると変更コストが高くなります。そこで専用のクラスにて対処します。
from unittest import TestCase import unittest import requests import os import boto3 HOSTNAME = os.environ['LOCALSTACK_HOSTNAME'] PORT = os.environ['EDGE_PORT'] def is_avaliable_localstack(): try: res = requests.get('http://localhost:4566', timeout=5) return res.json()['status'] == 'running' except Exception: return False @unittest.skipIf(not is_avaliable_localstack(), 'localstack not running') class SettingsLocalStack(TestCase): @classmethod def get_client(self, service): return boto3.client(service, endpoint_url=f'http://{HOSTNAME}:{PORT}/', region_name='us-east-1') @classmethod def get_resource(self, service): return boto3.resource(service, endpoint_url=f'http://{HOSTNAME}:{PORT}/', region_name='us-east-1')
継承して使うと、localstackを意識せずともlocalstackの状況に合わせて自動でSkipされます。
ローカル環境でlocalstackの起動完了を待つ
起動用に以下の内容でファイルを作成しておきます。
version: '2.1' services: localstack: image: localstack/localstack ports: - "4566:4566" - "4571:4571" - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}" environment: - SERVICES=${SERVICES- } - DEBUG=${DEBUG- } - DATA_DIR=${DATA_DIR- } - PORT_WEB_UI=${PORT_WEB_UI- } - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- } - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- } - DOCKER_HOST=unix:///var/run/docker.sock volumes: - "${TMPDIR:-/tmp/localstack}:/tmp/localstack" volumes: localstack:
localstackの起動時には以下のログを見ることができます。
% docker-compose up -d Creating tests_localstack_1 ... Creating tests_localstack_1 ... done
ポイントは、このログ表示時にHTTPリクエストを送っても確実に受け付けてくれるかは分かりません。Docker Desktopでログを見ると、Port待受処理が順次実行されている最中だとわかります。
そこで起動待ちとして、以下のようなバッチで実行してみます。
#!/bin/sh docker-compose down docker-compose up -d while [ "$(curl -s http://localhost:4566 | jq -r '.status')" != "running" ] do echo "booting.." sleep 2 done echo "waiting http://localhost:4566"
Stopping tests_localstack_1 ... done Removing tests_localstack_1 ... done Removing network tests_default Creating network "tests_default" with the default driver Creating tests_localstack_1 ... done booting.. booting.. booting.. booting.. waiting http://localhost:4566
これでバッチ実行終了時点でリクエストを受け付けてくれる状態とわかるようになりました。
なお、curlに-s
オプションを付けなかった場合、
Stopping tests_localstack_1 ... done Removing tests_localstack_1 ... done Removing network tests_default Creating network "tests_default" with the default driver Creating tests_localstack_1 ... done % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 curl: (52) Empty reply from server waiting % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 curl: (52) Empty reply from server waiting % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 curl: (52) Empty reply from server waiting % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 curl: (52) Empty reply from server waiting % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 curl: (52) Empty reply from server waiting % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 21 100 21 0 0 118 0 --:--:-- --:--:-- --:--:-- 118
のように進捗が大量に表示されます。省略したい場合は忘れずつけましょう。
CIでlocalstackがリクエストを受け付けるまで待ちを入れる
以下のようなStepをActionsで実行したとします。
- name: Test run: | docker-compose -f tests/docker-compose.yml up -d pipenv run test env: AWS_ACCOUNT_ID: "000000000000" AWS_ACCESS_KEY_ID: dummy-access-key AWS_SECRET_ACCESS_KEY: dummy-secret-key
ポイントはHTTPリクエストの受付を待たずにテストが実行されてしまい、SKIPとなる点です。
Creating network "tests_default" with the default driver Creating volume "tests_localstack" with default driver Pulling localstack (localstack/localstack:)... latest: Pulling from localstack/localstack Digest: sha256:81a7b7f12223fcd6c4f596baaf004c19e7a1f815887116c7f7f25962b7a7e89e Status: Downloaded newer image for localstack/localstack:latest Creating tests_localstack_1 ... Creating tests_localstack_1 ... done ============================= test session starts ============================== .. tests/test_example.py::CheckFinalized::test_check_finalized_0_1 SKIPPED [ 18%] tests/test_example.py::CheckFinalized::test_check_finalized_1_1 SKIPPED [ 19%] ..
ローカル環境での待機と同じものを入れてみます。
- name: Test run: | docker-compose -f tests/docker-compose.yml up -d while [ "$(curl -s http://localhost:4566 | jq -r '.status')" != "running" ] do echo "waiting" sleep 2 done pipenv run test env: AWS_ACCOUNT_ID: "000000000000" AWS_ACCESS_KEY_ID: dummy-access-key AWS_SECRET_ACCESS_KEY: dummy-secret-key
Creating network "tests_default" with the default driver Creating volume "tests_localstack" with default driver Pulling localstack (localstack/localstack:)... latest: Pulling from localstack/localstack Digest: sha256:bdfbe53666a4dd13a09dd9e4b155e2fb750b8041daf7efc69783cb4208b6cacc Status: Downloaded newer image for localstack/localstack:latest Creating tests_localstack_1 ... Creating tests_localstack_1 ... done waiting waiting waiting waiting ============================= test session starts ============================== .. tests/test_example.py::CheckFinalized::test_check_finalized_0_1 PASSED [ 18%] tests/test_example.py::CheckFinalized::test_check_finalized_1_1 PASSED [ 19%] ..
正常にテストされるようになりました。
あとがき
localstack/services/infra.pyの存在も認識はしていましたが、依存の解決が上手く行かなかったため今回の方法をとりました。
localstackを利用したテスト設計は最初こそ手間が掛かるものの、一度上手くいったら後はテストを追加するだけとなります。
AWSを利用したコードのテストには欠かせないライブラリです。慣れるまで少し掛かるかもしれませんが、根気よく付き合ってみることをおすすめします。