🌟
Lambda Pythonでmotoを使ったテスト手法
1. 結論(この記事で得られること)
この記事を読むと、以下が身につきます。
- motoライブラリを使ったLambda関数の単体テストの基本と実践パターン
- AWSサービス(S3/DynamoDB/SQS等)をモックする正しい設計
- テストコードがメンテナブルになる構造化テクニック
- AI(Claude/GPT)を使ってmotoのエラーを10分で解決する方法
- 本番デプロイ前にCI/CDで確実に検証するテスト戦略
私自身、過去にmotoを使わず「実際のAWSリソースに接続するテスト」を書いていた時期がありました。結果、テスト実行に数分かかる・並列実行できない・CI環境でクレデンシャル管理が煩雑になる、という三重苦に陥りました。
motoを正しく導入してからは、テストが数秒で完了し、開発速度が劇的に向上しました。
2. 前提(環境・読者層)
対象読者
- LambdaでPythonを使っているエンジニア
- AWSサービス連携のテストに苦労している方
- 単体テストと結合テストの境界線が曖昧で困っている方
前提環境
- Python 3.9 以上(Lambda実行環境に準拠)
- pytest 7.x 以上
- moto 4.x 以上
- boto3(AWS SDK for Python)
必要な知識
- Lambdaの基本的な実装経験
- pytestの基礎(fixture, parametrizeなど)
- boto3でのAWS操作経験
3. Before:よくあるつまずきポイント
3-1. 実AWSリソースに接続するテストの問題
# ❌ 危険なパターン:本物のS3に接続している
import boto3
def test_process_s3_file():
s3 = boto3.client('s3')
# テスト用バケットに依存...
s3.put_object(Bucket='test-bucket', Key='test.txt', Body=b'data')
result = my_lambda_handler({'bucket': 'test-bucket', 'key': 'test.txt'}, {})
assert result['statusCode'] == 200
問題点
- テスト実行に時間がかかる(ネットワークI/O)
- 並列実行でリソースが競合する
- AWSクレデンシャルの管理が煩雑
- CI環境で実AWSアカウントが必要になる
- 課金が発生する可能性
3-2. モック設計が曖昧で壊れやすい
# ❌ よくある失敗:部分的にモックして混乱
from unittest.mock import patch
@patch('boto3.client')
def test_lambda(mock_client):
# どのメソッドをモックするか統一ルールがない
mock_s3 = MagicMock()
mock_client.return_value = mock_s3
# テストが複雑化...
昔の私もこのパターンで書いていて、「このテスト、何をテストしてるんだっけ?」と後から見返して混乱した経験があります。
3-3. 環境変数やLambdaコンテキストの扱い
# ❌ 環境変数がハードコードされている
def lambda_handler(event, context):
bucket = 'production-bucket' # 環境変数で制御すべき
s3 = boto3.client('s3')
...
テスト時に環境変数を切り替えられず、プロダクションコードとテストコードの境界が曖昧になります。
4. After:基本的な解決パターン
4-1. motoを使った基本構成
# lambda_function.py
import boto3
import os
def lambda_handler(event, context):
bucket_name = os.environ.get('BUCKET_NAME')
s3 = boto3.client('s3')
key = event.get('key')
obj = s3.get_object(Bucket=bucket_name, Key=key)
body = obj['Body'].read().decode('utf-8')
return {
'statusCode': 200,
'body': f'Processed: {body}'
}
# test_lambda_function.py
import pytest
import os
from moto import mock_s3
import boto3
from lambda_function import lambda_handler
@pytest.fixture
def aws_credentials():
"""モック用の認証情報を設定"""
os.environ['AWS_ACCESS_KEY_ID'] = 'testing'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing'
os.environ['AWS_SECURITY_TOKEN'] = 'testing'
os.environ['AWS_SESSION_TOKEN'] = 'testing'
os.environ['AWS_DEFAULT_REGION'] = 'ap-northeast-1'
@pytest.fixture
def s3_client(aws_credentials):
"""モックS3クライアントを返す"""
with mock_s3():
yield boto3.client('s3', region_name='ap-northeast-1')
@pytest.fixture
def setup_s3_bucket(s3_client):
"""テスト用バケットを作成"""
bucket_name = 'test-bucket'
s3_client.create_bucket(
Bucket=bucket_name,
CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1'}
)
return bucket_name
def test_lambda_handler_success(s3_client, setup_s3_bucket):
"""正常系:S3からファイルを取得して処理"""
# Arrange
os.environ['BUCKET_NAME'] = setup_s3_bucket
test_key = 'test.txt'
test_body = 'Hello Lambda'
s3_client.put_object(
Bucket=setup_s3_bucket,
Key=test_key,
Body=test_body.encode('utf-8')
)
event = {'key': test_key}
context = {}
# Act
with mock_s3():
# motoのコンテキスト内でLambda実行
response = lambda_handler(event, context)
# Assert
assert response['statusCode'] == 200
assert 'Processed: Hello Lambda' in response['body']
ポイント
- 「@mock_s3()」デコレータでS3の振る舞いを完全にモック化
- fixtureで事前準備を構造化(再利用可能)
- 環境変数は「os.environ」で注入(テストごとに切り替え可能)
続きはnoteで
この記事の実装編・詳細解説はnoteで公開しています。
実際のコード例や、実務で遭遇するハマりポイントなど、より踏み込んだ内容を書いています。
Discussion