はじめに
TIG真野です。
AWS SDK for Go を使ったコードをクラウドサービスに依存無しでローカルにてテストするとき、次のような手法が考えられます。
- 外部アクセス部分をインタフェースにしてテスト時はモックコードに差し替え
- よく見る手法だが、テスト目的のみでインタフェースを作る手間がある
- SDKのmiddlewareを使用
- 詳細はAWS SDK for Go V2でinterfaceのモック”無し”でテスト - 365歩のテックを参考ください
- インタフェースの作成を抑えられるメリットがある
- LocalStackなどのモックサービスを利用
- 別プロセス(いわゆる、テストサイズはMedium)になるため。実行時間は1,2より増える。実環境に近い環境でテストできるため品質を上げやすい利点がある
- httptest.NewServer() でモックする
フューチャーで実績が多いのはLocalStackですが、例えばECS (Elastic Container Service)は2024年7月時点でProイメージでしか利用できません。
https://docs.localstack.cloud/references/coverage/coverage_ecs/
知らない方のために補足ですが、LocalStackには以下2種類のイメージが存在します。
- LocalStack Community イメージ
- LocalStack Pro イメージ
Proイメージを利用するためには認証トークンが必要で、そのためには有料プランの契約が必要です。その価値があるサービスであることは間違いないですが(ローカルでAWSのミニクラウドが動かせるって本当に凄い!)、基本的にはCommunityイメージに含まれるS3やSQS程度しか利用せず、少しだけ含まれないサービスを利用したいといった、悩ましいケースもあると思います。
先程の例であげたS3やSQSに加え、ECSにアクセスしたい場合は、Communityイメージの利用を継続しつつ、最初に上げた1または2で対応することが多いと思いますが、この記事では4つ目の手法を説明します。
httptest.NewServer() でモックする
例としてテスト対象であるユースケースやコントローラ層のコードから次のような、ecs RunTask
を含むRunTask()関数が呼んでいるとします。
type ECSClient struct { |
テストコードは、 http.NewServer()
を利用した次のようなコードを利用します。コードの説明は後述します。
import ( |
まず目に付くのは、httptest.NewServer()
内部のSwitch文でしょう。テスト対象のコードはECSの DescribeTaskDefintion
と RunTask
が呼ばれるため、振り分けのためにX-Amz-Target
ヘッダで分岐をしています(あまり知られていない気がしますが)AWSのエンドポイントはURLではなく、この独自ヘッダでRPC先を振り分ける仕様となっているためです。
AmazonEC2ContainerServiceV20141113.DescribeTaskDefinition
や AmazonEC2ContainerServiceV20141113.RunTask
の値はどうやって調べるんだという話ですが、デバッグ実行やパケットキャプチャの必要はなく、公式ドキュメントに記載しているので調べるだけです。
APIリファレンスのSample Requestにサンプルのリクエストが記載されているため、その値を設定します。
- https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html#API_RunTask_Examples
- https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTaskDefinition.html#API_DescribeTaskDefinition_Examples
振り分けられた先には、モックしたい応答結果として外部ファイルを指定します。このファイルの中身ですが、同様にAPIリファレンスの「Sample Response」を参考にします。
アプリケーションで利用しない項目はそのままで大丈夫です(もちろん、テスト上はなるべく本番に近しい値に書き換えた方が好ましいでしょう)。
最後は以下のブロックです。
awscfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion("ap-northeast-1"), |
SDKの向き先をhttp.TestServer()で起動したgoroutineに変更する必要があります。AWS SDK for Go V2では次のように config.WithEndpointResolverWithOptions()
で指定し、先ほど起動した ecsMockServer
の URL
フィールドを指定します。
この作成した awscfg
を検証対象のコードに設定すると、 http.TestServer()
で実装したAWSのモック応答を返してくれるようになります。
メリット
一覧でまとめました。1と比較するとインタフェースを追加すると言ったアプリコードの変更が不要。2との違いはMiddlewareの使い方に依存しなくても済む。3との違いは、LocalStack Communityイメージが対応していないサービスでも凌ぐことができる、といったことがあるかと思います。
No | Name | テストサイズ | インタフェース | エンドポイント | Memo |
---|---|---|---|---|---|
1 | SDKのインタフェース部分をモック化 | Small | 必要 | - | SDK自体の利用間違いは検知できない。アプリコードの変更が必要 |
2 | SDKが提供するMiddlewareを利用してモック化 | Small | - | - | Middlewareの利用方法を学ぶ必要 |
3 | LocalStackを利用 | Medium | - | 向き先を変更 | 品質は上げやすいが、テスト実行速度は遅くなる。対象によっては有料プラン加入が必要 |
4 | httptest.NewServer()を利用してモック化 | Medim | - | 向き先を変更 | クライアント側のコードに手を入れなくても良いが、サーバ側のレスポンスは決め打ちで作る必要がある |
まとめ
AWS SDK for Go を用いたアプリコードで、特にLocalStack Community版のイメージに含まれていないサービスを利用する時に、httptest.NewServer()
でgoroutineを起動させて、固定値をモックして応答する実装例をまとめました。
“X-Amz-Target” ヘッダの値でエンドポイントを切り替える方法、APIリファレンスからモックのJSONを返す手順などをまとめました。
この記事を読んで、自動テストのためだけにインタフェースを切ることになり、必要性は理解できるけど少しモヤモヤするなぁという方に別の選択肢を提示できたら嬉しく思います。