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

7c79a6c043825af5c538fb996430607c?s=47 Yuji Chino
September 13, 2020

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

9/12-13 に開催された「JAWS SONIC 2020 & MIDNIGHT JAWS 2020」のJAWS-UG 長野の発表です!
Motoは、ユニットテストでAWSサービスを簡単にmockできるPythonライブラリです。その基本的な使い方を、簡単なサーバーレスアプリケーションを例に紹介します!!!

7c79a6c043825af5c538fb996430607c?s=128

Yuji Chino

September 13, 2020
Tweet

Transcript

  1. AWSをMotoでmockして ユニットテスト! 2020/09/12 JAWS SONIC 2020 & MIDNIGHT JAWS 2020

    JAWS-UG 長野支部
  2. JAWS UG長野支部の紹介 長野支部は、長野の北側の長野市、真ん中の松本市で主に開催し ています。開催は不定期ですが、 - LT大会 - AWS認定 SAA教科書読書会  ※合格を目指しています!

    - AWS Expert Online(中継) - 味見会  ※ひとつのサービスを味見(触って)みる会です! など開催!あわせて、上越妙高支部との交流や、エバンジェリストの 方へきていただいたり県内の他コミュニティとも共同開催! 最近はご無沙汰・・・
  3. コアメンバー • 春原 宏保 ◦ 単身赴任で都内勤務のフリーランス ◦ ──のはずがコロナ禍で長野からリモート勤務中 ◦ 東京のアパートに払い続けるカラ家賃

    …… orz • 寺田 怜真 ◦ AWSでインフラ開発してます ◦ 一応学生もやってます(社会人ですが) ◦ 好きなサービスは、やっぱりLambda • 知野 雄二 ◦ PythonでWeb開発してます ◦ IoT好きでAWS IoTにお世話になってます
  4. コアメンバー

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

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

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

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

  9. Moto − Mock AWS Services • テストでAWSサービスを簡単にmockできるPythonライブラリ • https://github.com/spulec/moto •

    いろいろなサービスを簡単にmockできちゃいます! • 今回は基本的な使い方を、簡単なサーバーレスアプリケーションを 例に紹介します!!! • Motoを知って使うきっかけになってくれればうれしいです
  10. サーバーレス アプリケーション

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

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

    ⑦S3の画像情報を保存
  13. 開発環境 • Python ◦ バージョン:3.8 • ライブラリ ◦ boto3 ◦

    pytest ◦ Moto • 構成管理 ◦ serverless framework( https://www.serverless.com/ ) • ソース ◦ https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/
  14. ユニットテスト

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

  16. 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実行!
  17. 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
  18. 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の作成
  19. 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の作成
  20. 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 デコレータ追加 パラメータストア作成
  21. 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, ) パラメータストア作成
  22. デコレータを追加すればmock完了 あとはboto3を使用してこねこねする!

  23. 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実行! 移動先にあること
  24. Moto − 実行結果

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

  26. 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実行!
  27. 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' }, ], ・・・ ) テーブル作成
  28. もちろん先ほどと同じように デコレータを追加すればmock完了

  29. 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実行!
  30. Moto − 実行結果

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

  32. リソースの宣言を2回している • 今回、serverless frameworkを使用しています • ということは「serverless.yml」でリソースを宣言しています ◦ S3のBucket ◦ SQSのキュー

    ◦ DynamoDBのテーブル • ユニットテスト側でも作成をしている
  33. これ、ユニットテスト側も serverless.yml側を参照して 作成できないかな???

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

  35. pytest-serverless • motoを使用してserverless.ymlからリソースを自動的にmockしてくれるPythonラ イブラリ • https://pypi.org/project/pytest-serverless/ • これもデコレータつけるだけでmockしてくれて、リソース作成もしてくれる! ◦ @pytest.mark.usefixtures("serverless")

    • このため、紹介してきたコードのsetup部分が不要となる
  36. pytest-serverless • サポートしているサービス ◦ AWS::DynamoDB::Table ◦ AWS::SQS::Queue ◦ AWS::SNS::Topic ◦

    AWS::S3::Bucket
  37. pytest-serverless • motoからの差分 https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/pull/1

  38. 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
  39. pytest-serverless • 課題 - serverless.ymlから抜粋 https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/pull/1

  40. Enjoy AWS development with Python! • Boto3を始めPythonでAWSを開発する上で便利なライブラリは たくさんあります! ◦ 本当に感謝です!

    • 特にサーバーレスは色々なサービスを使用するので Motoは便利につかえるのではないでしょうか?