AWS Lambda 上で pytest を動かす
サービスの死活監視(DeepHealthCheck)や日次でのFullFunctionTestなど、定期的なテスト実行のために Lambda 上で pytest を動かすためのApplication雛形。
それなりにハマったのでメモ。
動作環境
- AWS Lambda: python3.6 runtime
- local環境は特に依存ないはず
AWS Lambda構成
超シンプルな構成。
Lambdaの構築自体は特に記載しないが、今回のサンプルを動かすだけなら下記設定で問題なし。
※ただし、lambaのタイムアウトがUIからペッと作ったままだとDefault 3秒 なのでおそらく足りない。せめて60秒くらいに延ばしておいたほうが良い。
また、テストの中でDynamoやS3を直接見に行くなど、テスト内容やテスト結果の保存先などによって、Lambdaに IAM role の追加が必要。
あと今回はただのサンプルなのでトリガの設定もしていないけど、実際使う場合にはもちろん何かしらトリガの設定が必要。
Application構成
. ├── lambda_function.py # AWS Lambdaのhandler ├── make_lambda_zip.sh # Lambdaにdeployするzipを作成するスクリプト ├── pytest.ini # pytestの設定 今回は固定optrion記載で使用 ├── requirements.txt # pytest実施に必要なpythonライブラリ └── tests/ # 実施するテスト群 └── sample_test.py
pytestの中身
上記treeの中で、pytest用のものは tests/
のみ。 (pytest.ini
と requirements.txt
は関係あるが後述)
tests/ 配下は自由にpytestで使用するテストコードを書けば良い
今回は例として sample_test.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import requests def test_simple_get_sample(): "simple get page request test" res = requests.get('https://www.google.com/') assert res.status_code == 200
googleにアクセスしてstatus_codeが200かを確認するだけのテスト。
conftest.py
や、他のテスト用src群などと組み合わせて大きめのtestを作ることも可能。
使用する python ライブラリたち
requirements.txt
にテストで使用するライブラリを記載していることが多いと思いますが、lambda上では pip install
が許可されていません。
なので、lambdaにdeployするzipに入れておく必要があります。
requirements.txt (参考までに今回向けのもの)
pytest requests
zip作成スクリプト
上記のライブラリインストールの件を受けて、zip作成はこんな感じ
make_lambda_zip.sh
#!/bin/bash pip install -r requirements.txt -t ./.requirements zip -rv lambda_function.zip ./
ここでは ./.requirements
pathにライブラリたちをインストールしている(他のコードとごちゃごちゃになるのを防ぐため)。ということは、ここにpathを通しておく必要がある。
また、毎回0からlib installを行う場合は、OSのtmp directoryコマンドなんかを使って書いておくと、冪等性が保てたり installしたlibを毎回削除できたりするので良いと思います。linuxだと mktemp
コマンドとか。
Lambdaのhandler
lambda_function.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import subprocess def lambda_handler(event, context): requirements_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), ".requirements") os.environ["PYTHONPATH"] = requirements_path res = subprocess.run(["python", ".requirements/pytest.py", "./tests"], env=os.environ, check=False, encoding="utf-8", stdout=subprocess.PIPE) print(res.stdout) return res.check_returncode()
ということで、環境変数 "PYTHONPATH" に .requirements
を追加するようにして、pytestを実行。
この書き方にしておくと、pytestの実行結果(success/failed)がlambda自体の成否に反映されます。
また、実行ログがlambdaのログとしてCloudWatchLogsに送信されるので、failした場合の解析もOK!
cacheの扱い
さて、ここまでの構成で Lambda を作成し、zipをdeployして動作させてみると、こんなエラーが発生。
OSError: [Errno 30] Read-only file system: '/var/task/.pytest_cache'
調査してみると、pytestは勝手に .pytest_cache
というdirectoryを作成するらしく、ここに書き込み・参照を行うと。
cacheのpathを tmp/.pytest_cache
とかに変更できればよいのですが、そういったoptionは見つからず。
※ tmp配下はlambda上で読み書きが許可されている
そこで、 cache を pytest 実行時に使用させないOptionを pytest.ini
に記載。
pytest.ini
[pytest] addopts = -p no:cacheprovider
ここまでで役者が全部出揃いました。
動作検証
lambdaを作成、上記 make_lambda_zip.sh で作成した zip を lambda に upload してテストしてみます。
(WebUI上の「テスト」で適当に実行。特に引数は不要なので、内容はなんでもOK)
更に、test失敗時のログ解析ができるかを検証するため、sample_test.py の期待するstatus_codeを 200 -> 500 に変更してテストしてみます。
失敗するので、「ログ」のリンクをたどってCloudWatchLogsのログを見に行きます。
失敗時のCloudWatchLogsから抜粋
============================= test session starts ============================== platform linux -- Python 3.6.1, pytest-3.6.2, py-1.5.3, pluggy-0.6.0 rootdir: /var/task, inifile: pytest.ini collected 1 item tests/sample_test.py F =================================== FAILURES =================================== ____________________________ test_simple_get_sample ____________________________ def test_simple_get_sample(): "simple get page request test" res = requests.get('https://www.google.com/') > assert res.status_code == 500 E assert 200 == 500 E + where 200 = <Response [200]>.status_code tests/sample_test.py:9: AssertionError =========================== 1 failed in 4.36 seconds ===========================
こんな感じで、ちゃんとテストの実行ログが残っていますね。これでテスト失敗時の解析もできそう!
参考リンク
- 【AWS】Lambdaでpipしたいと思ったときにすべきこと
- lambda上でpip install出来ない件
- Github/pytest: pytest2.8 invariantly writes to working directory + fails on readonly filesystem
- pytestのcacheの扱い
- Python3: 17.5. subprocess — サブプロセス管理
- subprocess の実行結果の扱い