motoをつかってboto3(AWS PythonSDK)スクリプトのテストを自動化する
動機
前回、LocalStackを使ってboto3のテストを実行してみて、リソース操作系スクリプトの動作確認を気軽に行えるようになった。
しかし、LocalStackは、EC2などのサービスには(2018年2月現在)未対応であったり、異常系のテストがエミュレートできないものが あったりと、現場で使うにはまだ改善点が多いことが分かったので、別の方法も考えてみることに。
で、情報を探してみたら、moto github.com
、、、というboto3のエミュレートを行えるライブラリを発見したので、使ってみた。
バージョン情報
Python 3.6.3
boto3 1.4.7
moto 1.2.0
対象のスクリプト
こんな感じの、「S3バケットを作成し、コマンドの実行結果を標準出力に出力する」ような スクリプトのテストを考えてみる。
import boto3 from botocore.exceptions import ClientError """ S3 にバケットを作成します。 """ bucket_name = 'kudarizakawonobore' SUCCESS = 0 ERROR = 1 def main(): client = boto3.client('s3') try: client.create_bucket(Bucket=bucket_name , CreateBucketConfiguration={ 'LocationConstraint': 'ap-northeast-1' } ) print('SUCCESS: バケットを作成しました') return SUCCESS except (ClientError) as e: print(e.response['Error']['Message']) error_code = e.response['Error']['Code'] if error_code == 'BucketAlreadyExists': print('ERROR: 他の人が使っている名前です') return ERROR else: print('予期せぬエラー') raise e if __name__ == '__main__': response = main() exit(response)
テスト側
上記のスクリプトを、正常系、異常系含めてテストするには、下記のようなテストを書く。
import boto3 import src.create_bucket as target import unittest from io import StringIO from unittest.mock import patch from moto import mock_s3 class TestCreateS3(unittest.TestCase): @mock_s3 def test_main(self): """正常系""" with patch('sys.stdout', new_callable=StringIO) as out: self.assertIs(target.main(), target.SUCCESS) print(out.getvalue()) self.assertTrue(out.getvalue().find('SUCCESS:')) @mock_s3 def test_main_error_bucket_already_exists(self): """すでに存在する場合はメッセージを表示して、ERRORコードを返す""" client = boto3.client('s3') client.create_bucket(Bucket=target.bucket_name, CreateBucketConfiguration={ 'LocationConstraint': 'ap-northeast-1' } ) with patch('sys.stdout', new_callable=StringIO) as out: self.assertIs(target.main(), target.ERROR) self.assertTrue(out.getvalue().find('ERROR: ')) if __name__ == '__main__': unittest.main()
やってること
motoを使うために必要な操作は二つで、テストケースでfrom moto import mock_s3
でmoto
をインポートするのと、
テスト用のメソッドに、 @mock_s3
とデコレータを付与すること。これだけ。
@mock_s3
のデコレータを付与されたメソッド内では、S3 関連の操作がすべてモック化される。
正常系のテストtest_main
では、S3バケットが一つも存在しない空間にS3のバケットを作成しにいくので、正常終了する。
一方、異常系のテストtest_main_error_bucket_already_exists
では、一度同名のバケットを作成してからスクリプトを
実行しているので、ClientErrorが発生して異常終了する。
ちょっとした問題点
異常系のパターンをすべて網羅しているわけではない。
例えば、S3のCreateBucketでは、BucketAlreadyOwendByYou のエラーが発生するパターンがエミュレートできない。
具体的にいうと、本来 motoを使わずスクリプトを2回実行する(すでに存在するバケットを再度作成する)と、BucketAlreadyExists(他の人が使っている)ではなく、BucketAlreadyOwnedByYou(自分がすでに所有している)エラーが発生するべきである。しかし、test_main__error_bucket_already_exists
で記述している通り、同一のコンテキストでバケットを2回作成したとしても、発生するエラーはBucketAlreadyExists となってしまう。
(motoのソースコードを少し見てみたが、 BucketAlreadyOwendByYouのエラーを発生させることはできなさそう。。。)
そのため、create_bucket
を行う前に、同名のバケットが存在しないか事前チェックを行うなど、実装側の方でカバーしたり、テストの書き方を工夫する必要がある。
LocalStackとの住み分け
このぐらい単純なスクリプトのテストだと、motoの方が簡単にテストできるので優れていそうだが、大量のリソースを操作するスクリプトや、CloudFormationの動作確認などでは、ダッシュボード機能でリソースの状態を確認できるLocalStackの方が活躍する場面が増えてくる(と思う)
どちらもまだ使いこなせていないので、引き続き学習していきます。