AKIBA.MAD 実践から学ぶ AWS Lambdaのテスト戦略 参加レポート

https://classmethod.connpass.com/event/209967/

かなり遅くなってしまったのですが、今回は2021/4/22に実施されたクラスメソッドさんのLambdaのテストに関するイベントに参加した際のメモを書きました。
メモは当日話を聞きながら残していたのですが、時間が経って記憶が曖昧な部分もありますので、何か間違いがあればご指摘ください。

# クラスメソッドMADとは

Modern Application Developmentチームの略で、最新のアーキテクチャを積極的に活用するサーバレス・コンテナ導入支援チームだそうです。

本イベントはクラスメソッドMADチームを中心に、クラスメソッドでサーバレスシステムを開発してるエンジニアが集まって座談会をしつつ、
参加者のサーバレスやテストに関する利用状況のアンケートを取ったり、質問に答えたりといった内容でした。

アンケートは勉強会の合間合間で取られていたのですが、文章で合間に書くと読みづらくなってしまうので、最後の方にまとめようかなと思います。


# テスト用語の解説

### Mock

テスト対象が呼び出す依存コンポーネントを擬似的に作成すること

### Spy

テスト対象が呼び出す依存コンポーネントへの呼び出し回数や入力値などを観測すること

### Fake

インメモリDBやLocalStackなど、実サービスと同等の動きをするツールを利用すること

↑このあたりの用語は切り分けづらいので、各ツール(Jestなど)ごとに切り分けが異なっていたりすることもあるとのことでした。
(特にMockとSpyが一緒くたにされていたりなど)

# テストの種類

クラスメソッドでは以下のように定義して行っているとのこと。

### Unit Test

各Lambdaのfunctionごとのテスト

### Integration Test

各Lambdaの入出力に対するテスト

Lambda Aの出力 → Lambda Bの入力等の部分まではやりそうなイメージでした。
(でないとUnit Testとの差別化ができないので)

### E2Eテスト

実際のサービス同士を組み合わせたテスト
例：Test Code→Amazon API Gateway→Lambda→DynamoDB

### 実際のテストのやり方

- DynamoDB等はローカルにFakeを作るか、開発環境を用いてテスト
- E2Eテストの後に全体を通してのテストは行う
  - 最後はやはりスプレッドシートでテストケースを作ることももちろんある
- E2Eテストも可能なところは自動化している
  - 例：Auth0が関わる部分はトークンを自動で発行してE2Eテストをする等

### 悩みポイント

- 非同期部分のテストは難しい
  - 特にIoT CoreはAWSアカウントごとにエンドポイントが1つだったりするので、テストがしづらい


# 質疑応答コーナー

### どこまで単体テストをするのか？

クリーンアーキテクチャの層を意識して層単位でUnit Testを書いてるとのこと。

- Entities
- Usecases
- Controllers・Gateways・Presenters
- Frameworks & Drivers(Web・UI・External Interfaces・DB・Devicesなど)

設計や実装の段階で階層構造を意識していれば、テストの責務もそれに合わせて切り分けられるということですね。



### テストデータやテストテーブルの管理について

- DynamoDBに流し込むテストデータの管理はJest等のツールでデプロイした環境にテストデータを流し込んでやってる
- CRUDのテストをやる時にデータを混ぜないようにしている
- Fakeのデータは事前にまとめて流し込んでいる
  - ある程度テストをパラレルで動かせない部分が出るのは仕方ないという割り切りも必要
- NoSQL Workbenchでテーブル定義を吐き出したりしてできなくはないが、実際のリソースと吐き出したテーブル定義での二重管理問題が出そう

### CI/CDでどこまでやるか

- CI/CDではユニットテストと、書けていればIntegrationテストまでを行う
- E2Eテストは非同期通信を伴うので、テストだと上手くいかなかったりする
  - 時間がかかったり、回線が弱いと思いもよらぬところで落ちたりもする

### テストに使える外部ツール

以下の2つが話題に出ていたので紹介しておきます。

#### MinIO
https://min.io/
https://openstandia.jp/oss_info/minio/

Go言語で実装されたAmazon S3クラウド・ストレージ・サービスと互換性のあるオブジェクト・ストレージ・サーバー。
本番環境ではAmazon S3を使用するが、開発時には、MinIOで開発／テストを行うなどの用途で利用することができる。

#### LocalStack pro

https://localstack.cloud/

クラウドアプリケーションを開発するための使いやすいテスト/モックフレームワークを提供してくれるサービス。
実際のAWSクラウド環境と同じ機能とAPIを提供するローカルマシン上のテスト環境を起動できる。

### プロジェクト内でのテストの強制について

VS CodeやEclipseのテストは強制はしてないが、CI/CDは強制している。
手元でのテストは強制はしていないが、実際は各自の手元でテストを一度回してからデプロイする人が多いとのこと。

### サービスレベルで落ちる場合のテストについて

- DynamoDBやAmazon S3そのものが落ちた場合は、テストコードレベルでは対処はできないことがあるので無理はしない
  - だが、ある程度エラーハンドリングで解決できるようにはしている
  - 500エラーが返るような時は追加調査を行う

AWSのリージョン単位で死ぬようなケースはある程度諦めも必要ですね。

### 単体テストで外部との通信を行うか

- ユニットテストは外部との通信は行わず、できる範囲で行う
- Googleのユニットテストの指標でもそうなっているとのこと
  - https://testing.googleblog.com/

- FakeはあくまでIntegrationテストまでで、シナリオテストで実際のデータを使う
  - シナリオテストは実際のログインからのシナリオ等を作るテストのこと
  - たとえば、Integrationテストまででは認証はテストに含めないみたいな割り切りも大事

E2Eテストは得られるものも多いが、時間がかかるのと、どこがダメだったのかを調べるにしても影響範囲が大きいので、Integrationテストの割合を徐々に増やしたいとのこと。
また、Fakeを作ってテストするのはDynamoDBだけなど、そこも割り切るのがいい

DynamoDBをマルチテーブルかシングルテーブルのどちらで用いてるかでFakeの使い方が変わってくるが、DynamoDBは現実のUsecaseと密結合な設計がいいと言われてる。


# 参加者アンケート

### 参加者のサーバレステストコードの利用状況について

- サーバレスをよく使っていて、なおかつテスト書いてる人は2割ぐらい
- サーバレスのコードは普段Pythonで書いてる人が6割ぐらい、Nodeが3割ぐらい
  - 正直Nodeがもうちょっと多いかなと思ってました。
  - NodeはTypeScriptでも書けるので、フロントエンドとの相性が良さそう
  - Pythonは機械学習やBoto3ライブラリを使ったAWSの別リソースとの連携などと相性が良さそう
  - 自分が普段書いてるRubyはサーバレスにおけるメリットはあまり無いかなと・・・

### 世の中のテスト状況

実際にサーバレスのテストを現在書いてる人がどの辺のテストを書いてるのかというアンケートでは、UnitTest(Mock/Spy)とE2Eテスト(AWS上)が50%超え

単体テストや結合テストを通しても、E2Eテストは最終的に必須になるという感じでした。

特に単体のテストから動作が変わりやすい点としては、IAMの権限で失敗や、SQS等のメッセージングサービスを組み合わせたりするタイミングが挙げられてました。

### 実際に使われてるテストツール

- LambdaテストはPytestが50%・Jestが27%
  - ここはPython勢が多いという事前の結果を踏まえてですかね。
- APIのvalidationはUnit Testで行っているが、E2Eでも400とか404とかのステータスが出ているかはある程度確認するようにしているとのこと

# まとめ

- マイクロサービス同士を組み合わせたサービス全体としての品質を担保するために重要な部分は当然手動でテストを行う必要がありますが、各マイクロサービスごとの品質の担保は自動テストでもかなり実現できそうだなと感じました。
  - マイクロサービス単位で動作検証ができていれば、全体テストではある程度細かい部分はマイクロサービスのテストに任せられるのはいいですね。
  - 疎結合やカプセル化の原則はマイクロサービスやそのテストでも活かせそうだと感じました。

- ただ、マイクロサービスにすると中身が隠蔽化されてしまい、開発のチーム分け等の都合によっては別のマイクロサービスの内部実装を全く知らないということもよくあると思うので、エラー出力の切り分けやそれに対するエラーハンドリングはより気をつけないといけなさそうだなと思いました。