今回はawsのDynamoDBに特化したお話。
実際、pytest-mockなんかを駆使すればいくらでもモッキングはできるのですが、
motoというbotoの処理をモック化してくれるライブラリがあるとのことなので、
早速使ってみた感じです。
直近ではDynamoDBをテストすることになったので、
とりあえずそれに特化したサンプルをご紹介しようかなと思います。
motoって?
Moto – Mock AWS Servicesとのことで公式はこちら。
ただ注意すべきなのは、全サービスをモック化してくれるわけではないという点。
対応表はこちら。
ここに載っていないサービスをモックにするのであれば、
pytestのMockFixtureを使用したりしてやるしかなさそうですね。
MockFixtureを使用したモックの仕方はこちらで記事にしてますのでご参照ください。
サンプル紹介
メインの処理
テスト対象となるメインの処理は以下になります。
import boto3
from boto3.dynamodb.conditions import Attr, Key
def moto_dynamo_sample_main(hoge_id: str) -> None:
"""サンプルアプリ"""
dynamodb = boto3.resource("dynamodb", region_name="ap-northeast-1")
dynamo_table = dynamodb.Table("sample_table")
# レコードを取得
response = dynamo_table.query(
KeyConditionExpression=Key("hoge_id").eq(hoge_id),
FilterExpression=Attr("ym").eq("202006"),
ScanIndexForward=False,
Limit=1,
)
# 取得したデータを加工して新規に追加する
update_data = response["Items"][0]
update_data["seq"] = update_data["seq"] + 1
update_data["data1"] = "foobar"
# dynamo書き込み
dynamo_table.put_item(Item=update_data)
今回アクセスするDynamoのテーブルは以下のようなイメージ。
| hoge_id(ハッシュキー) | seq(レンジキー) | ym | data1 |
| hoge | 1 | 202006 | hogehoge |
データの取得はymでフィルターかけてレンジキー降順で1件だけ取得みたいな感じです。
要するに最新のデータを取得して、データを加工して登録するような流れ。
テスト
これをテストする処理は以下。
from moto import mock_dynamodb2
from tests.dynamo_test_data import definition_mock_dynamo_table, test_data_01
from boto3.dynamodb.conditions import Attr, Key
from app.sample_main import moto_dynamo_sample_main
class TestSample:
@mock_dynamodb2
def test_motoでdynamodbをモックで使用する(self):
# モック定義
mock_table = definition_mock_dynamo_table()
[mock_table.put_item(Item=data) for data in test_data_01()]
# 処理実行
moto_dynamo_sample_main("hoge")
# 結果確認
response = mock_table.query(
KeyConditionExpression=Key("hoge_id").eq("hoge"),
FilterExpression=Attr("ym").eq("202006"),
ScanIndexForward=False,
Limit=1,
)
assert len(response["Items"]) == 1, "レコード取得件数"
actual_data = response["Items"][0]
assert actual_data["hoge_id"] == "hoge"
assert actual_data["ym"] == "202006"
assert actual_data["seq"] == 3
assert actual_data["data1"] == "foobar"
motoを使用する際はメソッドにデコレータをつけてあげます。
今回は「@mock_dynamodb2」のようにします。
こうすることでbotoのdyanamoリソースを操作するメソッドがモックになります。
このデコレータはclassの定義にもつけることができて、
その場合はclass内のメソッド全体に対してデコレコータが効果します。
注意すべきなのは、このデコレータをsetup_methodなどの
事前処理メソッドに付与したとしても、実際のテストメソッド側に
対してデコレータが効いていないとモックにならないということです。
※僕はこれを知らずawsにテーブルを作成してしまいました・・・・。
また、事前にテーブルの作成が必要で、今回はさらにそこにテストデータも必要です。
それをテストケースに書いていくとごちゃごちゃになるので、
今回は別ファイルにしました。
import boto3
def definition_mock_dynamo_table():
"""サンプルDynamoテーブル生成"""
mock_dynamodb = boto3.resource("dynamodb")
mock_table = mock_dynamodb.create_table(
TableName="sample_table",
KeySchema=[
{"AttributeName": "hoge_id", "KeyType": "HASH"},
{"AttributeName": "seq", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "hoge_id", "AttributeType": "S"},
{"AttributeName": "seq", "AttributeType": "N"},
],
ProvisionedThroughput={
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5,
},
)
return mock_table
def test_data_01():
"""テストデータ"""
item = [
{"hoge_id": "hoge", "ym": "202006", "seq": 1, "data1": "fuga"},
{"hoge_id": "hoge", "ym": "202006", "seq": 2, "data1": "piyo"},
]
return item
こうしておけばテーブル定義やデータが分離できて、
テストが見やすくなりますね。
まとめ
これでテスト実行するとちゃんとdynamoテーブルがモック化されて、
実際にawsへテーブルを作りにいくことなくテストを行うことができるようになります。
motoはまだまだ他のawsサービスもモック化できるようなので、
機会があればどんどん使っていきたいと思いました。
それでは!!
20代前半までは東京で音楽をやりながら両手の指以上の業種でアルバイト生活をしていましたが、某大手プロバイダのテレアポのバイトでPCの知識の無さに愕然とし、コンピュータをもっと知りたい!と思ったことをきっかけに25歳の時にITの世界に未経験で飛び込みました。
紆余曲折を経て、現在は個人事業主としてお仕事させていただいており、10年ほどになります。
web制作から企業システム構築、ツール開発など、フロントエンドもバックエンドもサーバーもDBAも依頼があれば何でもやってきた雑食系エンジニアです。
今風にいうとフルスタックエンジニアということになるのでしょうか??
→ 詳細プロフィールというか、生い立ちはこちら
→スキルシートをご覧になる場合はこちら
→お仕事のご依頼やお見積りなどお問い合わせはこちらから!

