Slide 1

Slide 1 text

AWSをMotoでmockして ユニットテスト! 2020/09/12 JAWS SONIC 2020 & MIDNIGHT JAWS 2020 JAWS-UG 長野支部

Slide 2

Slide 2 text

JAWS UG長野支部の紹介 長野支部は、長野の北側の長野市、真ん中の松本市で主に開催し ています。開催は不定期ですが、 - LT大会 - AWS認定 SAA教科書読書会  ※合格を目指しています! - AWS Expert Online(中継) - 味見会  ※ひとつのサービスを味見(触って)みる会です! など開催!あわせて、上越妙高支部との交流や、エバンジェリストの 方へきていただいたり県内の他コミュニティとも共同開催! 最近はご無沙汰・・・

Slide 3

Slide 3 text

コアメンバー ● 春原 宏保 ○ 単身赴任で都内勤務のフリーランス ○ ──のはずがコロナ禍で長野からリモート勤務中 ○ 東京のアパートに払い続けるカラ家賃 …… orz ● 寺田 怜真 ○ AWSでインフラ開発してます ○ 一応学生もやってます(社会人ですが) ○ 好きなサービスは、やっぱりLambda ● 知野 雄二 ○ PythonでWeb開発してます ○ IoT好きでAWS IoTにお世話になってます

Slide 4

Slide 4 text

コアメンバー

Slide 5

Slide 5 text

AWSをMotoでmockして ユニットテスト!

Slide 6

Slide 6 text

開発言語は 何を使用してますか? Java?Javascript?PHP?Python?Ruby?Go?COBOL?

Slide 7

Slide 7 text

● Python + boto3 ● サーバーレスあたりの開発を対象 このセッションについて

Slide 8

Slide 8 text

ユニットテストに Moto使ってますか?

Slide 9

Slide 9 text

Moto − Mock AWS Services ● テストでAWSサービスを簡単にmockできるPythonライブラリ ● https://github.com/spulec/moto ● いろいろなサービスを簡単にmockできちゃいます! ● 今回は基本的な使い方を、簡単なサーバーレスアプリケーションを 例に紹介します!!! ● Motoを知って使うきっかけになってくれればうれしいです

Slide 10

Slide 10 text

サーバーレス アプリケーション

Slide 11

Slide 11 text

サーバーレスアプリケーション 【構成】 S3 Lambda SQS System Manager Parameter Store DynamoDB

Slide 12

Slide 12 text

サーバーレスアプリケーション 【構成】 ①画像アップロード ②S3トリガーで起動 ③S3の画像を移動 ④パラメータストア Queue名を取得 ⑤S3の画像情報を 送信 ⑥SQSトリガーで起動 ⑦S3の画像情報を保存

Slide 13

Slide 13 text

開発環境 ● Python ○ バージョン:3.8 ● ライブラリ ○ boto3 ○ pytest ○ Moto ● 構成管理 ○ serverless framework( https://www.serverless.com/ ) ● ソース ○ https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/

Slide 14

Slide 14 text

ユニットテスト

Slide 15

Slide 15 text

対象 【構成】 ③S3の画像を移動 ④パラメータストア Queue名を取得 ⑤S3の画像情報を 送信

Slide 16

Slide 16 text

Moto − S3 from moto import mock_s3, mock_ssm, mock_sqs from handler import s3_trigger_and_move ・・・ @mock_s3 @mock_sqs @mock_ssm def test_s3trigger_and_move_success(s3_trigger_event): """ 画像の移動が正常に行われ、 SQSへS3の情報を送信すること """ # setup bucket_name = s3_trigger_event['Records'][0]['s3']['bucket']['name'] obj_key = s3_trigger_event['Records'][0]['s3']['object']['key'] setup_s3(bucket_name, obj_key=obj_key) queue_name = 'mock-sample-queue-dev' setup_sqs(queue_name) setup_parameter_store('mock_sqs_queue_name', queue_name)  # 実行  s3_trigger_and_move(s3_trigger_event, '')  ・・・ ソース: https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/blob/master/tests/test_s3_trigger_and_move.py デコレータ追加 Bucket、画像をPUT Lambda実行!

Slide 17

Slide 17 text

Moto − S3 def setup_s3(bucket_name, obj_key=None): # bucket s3 = boto3.resource('s3') bucket = s3.Bucket(bucket_name) bucket.create() # put if obj_key: # put image data = open(os.path.join(get_abspath(), 'GL_LOGO.jpg'), mode='rb') result = bucket.put_object(Key=obj_key, Body=data) boto3を使用してBucket作成 アップロードされた画像 をPUT

Slide 18

Slide 18 text

Moto − SQS from moto import mock_s3, mock_ssm, mock_sqs from handler import s3_trigger_and_move ・・・ @mock_s3 @mock_sqs @mock_ssm def test_s3trigger_and_move_success(s3_trigger_event): """ 画像の移動が正常に行われ、 SQSへS3の情報を送信すること """ # setup bucket_name = s3_trigger_event['Records'][0]['s3']['bucket']['name'] obj_key = s3_trigger_event['Records'][0]['s3']['object']['key'] setup_s3(bucket_name, obj_key=obj_key) queue_name = 'mock-sample-queue-dev' setup_sqs(queue_name) setup_parameter_store('mock_sqs_queue_name', queue_name)  # 実行  s3_trigger_and_move(s3_trigger_event, '')  ・・・ ソース: https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/blob/master/tests/test_s3_trigger_and_move.py デコレータ追加 Queueの作成

Slide 19

Slide 19 text

Moto − SQS def setup_sqs(name): sqs = boto3.resource('sqs', region_name='ap-northeast-1') return sqs.create_queue( QueueName=name, Attributes={ 'VisibilityTimeout': '660' }, ) Queueの作成

Slide 20

Slide 20 text

Moto − Parameter Store from moto import mock_s3, mock_ssm, mock_sqs from handler import s3_trigger_and_move ・・・ @mock_s3 @mock_sqs @mock_ssm def test_s3trigger_and_move_success(s3_trigger_event): """ 画像の移動が正常に行われ、 SQSへS3の情報を送信すること """ # setup bucket_name = s3_trigger_event['Records'][0]['s3']['bucket']['name'] obj_key = s3_trigger_event['Records'][0]['s3']['object']['key'] setup_s3(bucket_name, obj_key=obj_key) queue_name = 'mock-sample-queue-dev' setup_sqs(queue_name) setup_parameter_store('mock_sqs_queue_name', queue_name)  # 実行  s3_trigger_and_move(s3_trigger_event, '')  ・・・ ソース: https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/blob/master/tests/test_s3_trigger_and_move.py デコレータ追加 パラメータストア作成

Slide 21

Slide 21 text

Moto − Parameter Store def setup_parameter_store(name, value, value_type='String'): ssm = boto3.client('ssm', region_name='ap-northeast-1') ssm.put_parameter( Name=name, Value=value, Type=value_type, Overwrite=True, ) パラメータストア作成

Slide 22

Slide 22 text

デコレータを追加すればmock完了 あとはboto3を使用してこねこねする!

Slide 23

Slide 23 text

Moto − 検証 # 実行 s3_trigger_and_move(s3_trigger_event, '') # アップロード先には画像がないこと try: _ = s3.get_object(Bucket=bucket_name, Key=obj_key) except ClientError as e: # 移動したため、移動元は画像なし assert e.response['Error']['Code'] == 'NoSuchKey' # 移動先に画像があること image_filename = obj_key.split('/')[-1] to_obj_key = f'finish/{image_filename}' response = s3.get_object(Bucket=bucket_name, Key=to_obj_key) assert response['ResponseMetadata']['HTTPStatusCode'] == 200 # Queueのメッセージが正常にセットされていること msg_list = get_sqs_messages(queue_name) assert len(msg_list) == 1 message_body = json.loads(msg_list[0].body) assert len(message_body) == 2 assert message_body['bucket_name'] == bucket_name assert message_body['obj_key'] == obj_key ソース: https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/blob/master/tests/test_s3_trigger_and_move.py 移動元にないこと Queueにセットされていること Lambda実行! 移動先にあること

Slide 24

Slide 24 text

Moto − 実行結果

Slide 25

Slide 25 text

対象 【構成】 ⑦S3の画像情報を保存

Slide 26

Slide 26 text

Moto − DynamoDB from moto import mock_dynamodb2 ・・・ @mock_dynamodb2 @freezegun.freeze_time('2020-09-12 18:05:59') def test_sqs_trigger_and_save(sqs_trigger_event): # setup message_body = json.loads(sqs_trigger_event['Records'][0]['body']) bucket_name = message_body['bucket_name'] obj_key = message_body['obj_key'] table_name = 's3-info-dev' setup_dynamodb_s3_info(table_name) # 実行 sqs_trigger_and_save(sqs_trigger_event, '')  ・・・ ソース: https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/blob/master/tests/test_sqs_trigger_and_save.py デコレータ追加 テーブル作成 Lambda実行!

Slide 27

Slide 27 text

Moto − DynamoDB def setup_dynamodb_s3_info(table_name): dynamodb = boto3.resource(service_name='dynamodb', region_name='ap-northeast-1') dynamodb.create_table( TableName=table_name, KeySchema=[ { 'AttributeName': 'bucket_name', 'KeyType': 'HASH' }, { 'AttributeName': 'timestamp', 'KeyType': 'RANGE' }, ], ・・・ ) テーブル作成

Slide 28

Slide 28 text

もちろん先ほどと同じように デコレータを追加すればmock完了

Slide 29

Slide 29 text

Moto − 検証 # 実行 sqs_trigger_and_save(sqs_trigger_event, '') # テーブルにデータが保存されていること timestamp = int(time.mktime(datetime.now().timetuple())) table = dynamodb.Table(table_name) key = { 'bucket_name': bucket_name, 'timestamp': timestamp, } response = table.get_item(Key=key) item = response['Item'] assert item['bucket_name'] == bucket_name assert item['timestamp'] == timestamp assert item['obj_key'] == obj_key ソース: https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/blob/master/tests/test_sqs_trigger_and_save.py 保存内容を確認 Lambda実行!

Slide 30

Slide 30 text

Moto − 実行結果

Slide 31

Slide 31 text

コードを紹介しましたけど 冗長な部分あるなぁ って思いました?

Slide 32

Slide 32 text

リソースの宣言を2回している ● 今回、serverless frameworkを使用しています ● ということは「serverless.yml」でリソースを宣言しています ○ S3のBucket ○ SQSのキュー ○ DynamoDBのテーブル ● ユニットテスト側でも作成をしている

Slide 33

Slide 33 text

これ、ユニットテスト側も serverless.yml側を参照して 作成できないかな???

Slide 34

Slide 34 text

なんと便利なライブラリがあるんです!

Slide 35

Slide 35 text

pytest-serverless ● motoを使用してserverless.ymlからリソースを自動的にmockしてくれるPythonラ イブラリ ● https://pypi.org/project/pytest-serverless/ ● これもデコレータつけるだけでmockしてくれて、リソース作成もしてくれる! ○ @pytest.mark.usefixtures("serverless") ● このため、紹介してきたコードのsetup部分が不要となる

Slide 36

Slide 36 text

pytest-serverless ● サポートしているサービス ○ AWS::DynamoDB::Table ○ AWS::SQS::Queue ○ AWS::SNS::Topic ○ AWS::S3::Bucket

Slide 37

Slide 37 text

pytest-serverless ● motoからの差分 https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/pull/1

Slide 38

Slide 38 text

pytest-serverless ● 課題・・・ ○ 今回リソース名をstageで置換するようにしたのですが、、、 pytest-serverless側でリソース作成時 にそのままの値で作成をおこなっているらしく、、、リソース作成でエラーとなってしまいます・・・ ■ 例:bucket: mock-aws-with-moto-${opt:stage} ○ もし、解決方法や、別の方法を知っている方がいれば教えてほしいです ○ moto→pytest-serverlessに対応した場合の差分はこちら ■ https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/pull/1

Slide 39

Slide 39 text

pytest-serverless ● 課題 - serverless.ymlから抜粋 https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/pull/1

Slide 40

Slide 40 text

Enjoy AWS development with Python! ● Boto3を始めPythonでAWSを開発する上で便利なライブラリは たくさんあります! ○ 本当に感謝です! ● 特にサーバーレスは色々なサービスを使用するので Motoは便利につかえるのではないでしょうか?