2020-09-14
Moto で CDK プロジェクト内の Lambda 関数をテストする #jawssonic2020
先日開催された 24時間の技術イベント JAWS SONIC 2020 のセッション内で初めて知った Moto を試してみました。
イベントページはこちら。
│JAWS SONIC 2020 & MIDNIGHT JAWS 2020
イベントは 24 時間開催ということで、 Twitter でも #jawssonic2020 のハッシュタグで大盛りあがりでした。運営のみなさま、登壇されたみなさま、本当にお疲れさまでした & ありがとうございました!
目次
JAWS-UG 長野支部のセッション
今回試す Moto に関するセッションは、 JAWS-UG 長野支部の知野さん、寺田さん、春原さんによるセッション 「AWSをMotoでmockしてユニットテスト!」 でした。登壇資料は Speakerdeck で公開されています。
Moto とは
Moto とは、 AWS の各サービスをモックできる Python のライブラリです。 Moto を使うことで AWS の各サービスを操作する Python のプロジェクトで、 AWS にアクセスする部分のテストを簡単に実装することができます。
https://github.com/spulec/moto
使ってみる
セッション内ではサーバーレス構成でのテストということで、 S3、 SQS、 SSM Parameter Store に関する例が紹介されていまいた。今回は、 EC2 に対する操作に関するテストを実装してみます。
対象のプロジェクト
今回 対象にするプロジェクトは、だいぶ前に作った下記の CDK プロジェクトです。
https://github.com/michimani/auto-start-stop-ec2
特定のタグを持つ EC2 インスタンスを自動起動・停止させる Lambda 関数と EventBridge のルールをデプロイする CDK プロジェクトです。今回はこのプロジェクト内の Lambda 関数を、 Moto を使ってテストしてみます。また、テストコードの実行には pytest を使います。
このアプリケーションの詳細については下記の記事で書いてます。
https://michimani.net/post/aws-auto-start-stop-ec2-stack-using-cdk/
テストコード
では早速 実装したテストコードを見てみます。
import boto3
import importlib
from moto import mock_ec2
MOCK_REGION = 'ap-northeast-1'
MOCK_AMI_ID = '/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-ebs'
MOCK_INSTANCE_TYPE = 't3.nano'
MOCK_INSTANCE_NAME = 'mock_instance'
@mock_ec2
class TestStartStopEc2:
assec2 = importlib.import_module('lambda.auto-start-stop-ec2')
ec2_client = None
mock_instance = {}
def setup_method(self, method):
self.ec2_client = boto3.client('ec2', region_name=MOCK_REGION)
res = self.ec2_client.run_instances(ImageId=MOCK_AMI_ID,
InstanceType=MOCK_INSTANCE_TYPE,
MinCount=1,
MaxCount=1,
TagSpecifications=[
{
'ResourceType': 'instance',
'Tags': [
{
'Key': 'AutoStartStop',
'Value': 'TRUE'
},
{
'Key': 'Name',
'Value': MOCK_INSTANCE_NAME
}
]}
])
self.mock_instance = {
'instance_id': res['Instances'][0]['InstanceId'],
'instance_name': MOCK_INSTANCE_NAME
}
def teardown_method(self, method):
self.ec2_client.terminate_instances(
InstanceIds=[self.mock_instance['instance_id']])
def test_stop_ec2_instance(self):
res = self.assec2.stop_instance(self.ec2_client, self.mock_instance)
assert res is True
def test_start_ec2_instance(self):
res = self.assec2.start_instance(self.ec2_client, self.mock_instance)
assert res is True
Moto でモックするにはデコレータを付与するだけ
Moto では、モックしたいサービスを操作する部分 (関数、クラス) に対してデコレータをつけることで、そのサービスをモックすることができます。今回はテストコードのクラス TestStartStopEc2 に対して EC2 をモックするための @mock_ec2 デコレータをつけます。
setup/teardown でモック用インスタンスの起動と削除
クラス内の setup_method() および teardown_method() は、それぞれテストメソッドの実行前後に実行されるメソッドです。なので、 setup_method() 内でモック用の EC2 インスタンスを起動し、 teardown_method() 内でそのインスタンスを削除 (terminate) しています。
setup_method() ではインスタンスを起動したあと、起動したインスタンスの InstanceId と Instance Name (Tags の Name の値) をクラス変数に保持し、後続のテストメソッド、および teardown_method() で使用できるようにしています。
実行
テストの実行は pytest を使用します。
$ python -m pytest ./lambda/test/test_start_stop_ec2.py -v
============================================== test session starts ===============================================
platform darwin -- Python 3.8.5, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- .venv/bin/python
cachedir: .pytest_cache
rootdir: /path/to/Projects/auto-start-stop-ec2
collected 4 items
lambda/test/test_start_stop_ec2.py::TestStartStopEc2::test_describe_instances PASSED [ 25%]
lambda/test/test_start_stop_ec2.py::TestStartStopEc2::test_get_target_ec2_instances PASSED [ 50%]
lambda/test/test_start_stop_ec2.py::TestStartStopEc2::test_stop_ec2_instance PASSED [ 75%]
lambda/test/test_start_stop_ec2.py::TestStartStopEc2::test_start_ec2_instance PASSED [100%]
テストの実行もできました。
まとめ
AWS の各サービスをモックできる Python のライブラリ Moto を使って CDK プロジェクト内の Lambda 関数をテストしてみた話でした。
デコレータをつけるだけで AWS の各サービスをモックできるのは、非常に簡単で便利だなと感じました。今後も Python でアプリケーションを実装する際には、 Moto の利用を考えたいと思います。
JAWS SONIC 2020 のセッション内では他にも初めて知る内容や試してみたい内容があったので、記憶が新しいうちに試していこうと思います。