Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Yuji Chino
September 13, 2020

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

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

Yuji Chino

September 13, 2020
Tweet

More Decks by Yuji Chino

Other Decks in Programming

Transcript

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

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

    …… orz • 寺田 怜真 ◦ AWSでインフラ開発してます ◦ 一応学生もやってます(社会人ですが) ◦ 好きなサービスは、やっぱりLambda • 知野 雄二 ◦ PythonでWeb開発してます ◦ IoT好きでAWS IoTにお世話になってます
  3. Moto − Mock AWS Services • テストでAWSサービスを簡単にmockできるPythonライブラリ • https://github.com/spulec/moto •

    いろいろなサービスを簡単にmockできちゃいます! • 今回は基本的な使い方を、簡単なサーバーレスアプリケーションを 例に紹介します!!! • Motoを知って使うきっかけになってくれればうれしいです
  4. 開発環境 • Python ◦ バージョン:3.8 • ライブラリ ◦ boto3 ◦

    pytest ◦ Moto • 構成管理 ◦ serverless framework( https://www.serverless.com/ ) • ソース ◦ https://github.com/peacemaker07/jawsug_sonic_midnight_jaws/
  5. 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実行!
  6. 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
  7. 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の作成
  8. 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の作成
  9. 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 デコレータ追加 パラメータストア作成
  10. 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, ) パラメータストア作成
  11. 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実行! 移動先にあること
  12. 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実行!
  13. 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' }, ], ・・・ ) テーブル作成
  14. 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実行!
  15. 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
  16. Enjoy AWS development with Python! • Boto3を始めPythonでAWSを開発する上で便利なライブラリは たくさんあります! ◦ 本当に感謝です!

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