この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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を利用したコードのテストには欠かせないライブラリです。慣れるまで少し掛かるかもしれませんが、根気よく付き合ってみることをおすすめします。