E2Eのあとの全体テストの有無

佐藤智樹氏(以下、佐藤):視聴者の方でも(Lambdaを)大量に使っている方は、「ここを困っていました」とか言ってもらえると共感できそうです。

新井成一氏(以下、新井):願わくば、公式でそういうFakeを出してもらうのが、僕は一番いいんじゃないかと思っています。

佐藤:確かに。LocalStackとかありますが、IoTサービス系とか「IoT」とかだけ書いてあって。これは何が使えるのかな。すみません、なんか言い過ぎた気が……。

(一同笑)

和田祐介氏(以下、和田):これは叩けばもっと出てきそうだ。

佐藤:ちょっと取っておきますか。

新井:ではせっかくなので、質問がきているのでいくつか答えてみますか? 「E2Eテストのあとに全体テストがあるのでしょうか?」。和田さん、まずこれどうですか?

和田:やっています。実際にはAPIだけで完結するシステムもあると思いますが、それ以外の画面はくっついてくる話だと思うので。APIに対するテストは、コードを書いてJESTなどをやっていますが、僕の携わっている案件は、古き良きスプレッドシートにテストケースを書いて、愚直に目視で確認していくようなパターンもけっこう多いです。

画面はいろいろフィードバックがあって、ガチャガチャ動かしたりすることもあります。コード化してどれだけうれしいかを考えたときに、自分の感覚の中の話になりますが、まだ労力のわりには得られるものが追いついていない感触があるので。E2Eテストのあとは画面テストをやっていますが、今はちょっとテストケースを愚直に書いてやっています。

新井:なるほど。画面という観点もあると思いますが、マイクロサービスとして1つサービスとして作ったものの、APIがあって、別のサービスから呼び出されるようなケースもあるのかなと思って。そうなってくると、E2Eのあとのテストはけっこうしんどいのかなと少し思ったりもしました。

和田:そうですね。今のところ、そこは画面のテストで包含している感じです。ファクトやマイクロサービス間のテストの考え方は、一般的にはもちろんありますが、やはり限られた期間、限られた予算の中でどこまでできるかを考えたときに、ちょっと今は画面側に任せてしまっているところも多いです。

E2Eテストの自動化

新井:なるほど。ありがとうございます。次、「E2Eテストも自動化を行っているのでしょうか?」。これは加藤さんどうですか?

加藤諒氏(以下、加藤):はい、行える範囲でがんばって自動化しています。もうちょっと話を広げると、認証まわりはふだんAuth0などのSaaSを使うことが多いです。クレデンシャルなどを使い、いい感じにIDトークン、アクセストークンを発行できるようにして、実際にそのトークンを使って、認証をかけながらE2Eを自動でやることをがんばっています。

新井:認証部分も含めて、E2Eでなるべく自動化してやっている感じですね。

加藤:そうです。

新井:ありがとうございます。次、「どこまで単体テストするかの基準はあるのでしょうか?」。これはわかりみの深い質問ですね。これは佐藤さんお願いしてもいいですか?

佐藤:めっちゃ嫌なんですけど、この質問(笑)。実際にコードを書くときには、和田さんが書いていたハンドラ層、ユースケース層、インフラ層みたいなものに分けてLambdaのコードを書いて、その層単体ぐらいはテストはします。インテグレーション的なものは、今はちょっと案件で書いていますが、ハンドラ全体をとおしては書かなかったりとに分かれる感じです。

新井:今画面に表示しているレイヤーの層の内部は、ユニットテストの対象にする感じですね。

佐藤:そうですね。層の内部で閉じるようにテストを書くような、モックなどを使ってやることをやっています。それだと結局SDKのテストがうまくできなくて、DynamoDBに送るパラメータが間違ってこけることがけっこうあったなと。

新井:SDKが絡む部分は、本当に悩みどころですね。

佐藤:DynamoDB以外はそんなにないかと思いますが。DynamoDBは自分がパラメータをぜんぜん覚えられなくて、毎回「あれ? こういう感じじゃなかったっけ?」みたいな。書き方を間違えて失敗することがけっこうあったので。

加藤:基本、ユニットテストをがんばって書く派ですが、リポジトリ層のDynamoDBは、ユニットテストを書いても意味がないので、「Fakeでテストするでいいや」と書きません。

佐藤:そうですよね。あまりできていませんでしたが、そのほうが効率が良さそうな。

DynamoDBに流し込むテストデータ

新井:次ですね。「DynamoDBに流し込むテストデータはどうやってますか?」。これは和田さんどうですか?

和田:ほとんどのテストで愚直にJESTなど、before(:all)やbefore(:each)でテストの事前準備を定義できると思いますが、そこでテストデータを実際に作っています。

僕はいわゆるLocalStackなどはあまり使わず、実際にデプロイした環境でE2Eテストをやってしまいます。もちろん既存のデータをバッティングしないように気をつけて、実際の環境にテストデータを流し込んで、そのデータに対して実際にAPIを叩いてGETできるか、createできるかなどのテストはやります。

新井:なるほど。テストの前後処理でけっこうしっかりテストデータの作成とクリアをちゃんとやれば、いける感じですね。

和田:そうですね。あえて言うことがあるとすれば、CRUDのAPIをよく作ると思いますが、GETのAPIをテストするときに、CREATEのAPIを使わないことは気をつけています。それぞれ独立してテストをできるようにするのはやっています。だから、直接DynamoDBのSDKを呼んで、PutItemでアイテムを作ってからGET APIを呼ぶようなことをやっています。ちょっと回答になっているか微妙ですが、そんな感じです。

新井:なるほど。確かに事前にテストコード内でデータの前後処理のようなものをやるのもありかとは思いますが、シナリオテストのようなかたちでGETを呼んで、みたいなものもあるのかなと聞いていて思いました。

和田:そうですね。そういうのもやる価値はあると思います。

FakeのDynamoDBテーブルの定義

新井:回答になっていることを期待して、これは完了にします。どんどん行きましょう。「FakeのDynamoDBテーブルの定義はどう管理していますか?」。これは加藤さんどうですか?

加藤:今の和田さんの回答とけっこうつながってくると思いますが、私は普通にテスト用のコードの中で、beforeAllみたいなところで、Fakeに対してテーブルを作ってデータを投入してを、開発している言語で書いています。

本来テストはパラレルで動くようにしたほうがいいとは思いますが、いちいちテーブルなどを作ってデータを投入している関係で、Fakeを触るような部分はパラレルでは動かなくなってしまいますが、そこは割り切ってます。たぶんもうちょっとがんばって、テストごとに個別のテーブル生成などをすれば、パラレルでもできなくはないと思います。

新井:これはもしかしたら意図としては、テーブル定義のようなものをテストコード側で管理しているのか、CDKの内部のコードをうまくパースしてテストコードで使うようなことを想定しているのかな、と若干思っているんですが。

加藤:そういう意味で言うと、個別管理しています。

新井:実際に、何かCDKのコード側で管理するようなことってできたそうですか?

加藤:例えば、AWSが出しているNoSQL Workbenchという、DynamoDBの定義などをGUIで作ってJSONに吐き出せるものがあるので、それで定義を作ってJSONで吐き出して、テスト側でもCDK側でも読み込むようなライブラリを作ればできるとは思います。ただ、けっこうGSIまわりはCloudFormationのつらいところで、一度に2つ変更できないなどがあったりするので、スクリプトで自動でやるようなことはたぶんきつくなると思います。

新井:確かに。JSONで管理し出すと、CDKのうまみなどがなくなってしまう気もします。

加藤:あと、たぶん今言った複数デプロイ。結局「あ……」みたいなことになる。

和田:あまりそういうことを考えたくないから、僕はFakeなどはあまり使わないところは正直あります。

(一同笑)

CI/CDテストの種類

新井:なるほど。回答ありがとうございます。次です。「CI/CDはどのテスト種類を実施していまか?」。これは佐藤さんどうですか?

佐藤:自分はCI/CDはユニットテストだけをやっています。インテグレーションテストも、組んでいるときはやっています。E2Eに関しては、手動でやっています。非同期実行のせいで、どうしても安定しないところがあって。ネットワークの原因などもあるようですが、そういうのがどうしてもあるので。E2Eは自動のみ載せて本当にうまくいくのかはちょっと怪しくて、そのままやっていませんでした。

和田:「非同期だからうまくいかないことがある」というのは、具体的にどういう処理ですか? 

佐藤:単純に、API Gatewayを叩いてLambdaを実行して、DynamoDBでデータを取ってくることですが、うまくいかないときがあって。

和田:なるほど。3回チャレンジなどは特に入れていないですか?

佐藤:やっていなかったです。そのときは、1回チャレンジして失敗したらエラーみたいな。

和田:E2E要素がけっこうそうですね。何回までチャレンジできるようにするのは、けっこう便利ですね。

佐藤:その観点はありますね。どうでもいい話ですが、昔、家のネットの回線が昔悪くて、そのせいでE2Eが見たこともないエラーで失敗することもかなりあって。SDKで書き込みに行くのが失敗するような。foreachで書こうとするけど、それで失敗していて、よくわからない。

和田:なるほど。IoTの世界って、潤沢なネットワーク帯域があるとは限らない場合もあるので、そういうケースでのテストもできるという考え方もできますよね(笑)。

佐藤:そうですね(笑)。お客さまにもそういうことを言わましたが、「そもそもテストできないからどうしよう」みたいな。

和田:開発するという観点では、すごくやりにくいと思います。すみません(笑)。ありがとうございます。いったんこれは完了にして、「E2E テスト実施時には、mablなどのツールを使用しているのでしょうか?」。これ知っている方いますか? 知らなくてすみません。みなさんよく知っていますね。

加藤:これはフロントから全部できるやつなのかな。私たちはバックエンドエンジニアなので、APIのテストにけっこう突出していることが多くて。フロントからテストすることもなくはないけれど、さっき和田さんが言ったようにふだんから頻繁にはしないから、スプレッドシートで書いて、手でチャックしていることが多いかと思います。

新井:なるほど。我々はあまり使わない感じですね。

和田:ちょっと手軽に試せるものがあって、加藤諒さんが試していたのがあったと思います。Auth0の認証まわりなどで、けっこう苦労していましたよね。

加藤:ドメインが切り替わったりとかしたら、けっこう大変だった記憶があります。

Lambaのユニットテスト

新井:「Lambdaのユニットテストですが、VSCodeやEclipseでテストをしていますか?」。これは、じゃあ加藤さん。

加藤:私は、プルリク(プルリクエスト)を上げる前に、手元でテストをしています。ただ、あまり強制はしなくてもいいんじゃないかなと思っていて。プルリクがマージされる前に強制的に実行できるようになっていれば、それでいいんじゃないかと思います。ローカルで動かすのが嫌だと思っている人もいると思うので。CI/CD上では、絶対強制的にテストをさせる。普通に手元でも回せるように書いているので、私は一応手元で回します。

佐藤:自分も手元で一応動かせるようにしてCI/CDで組んでおいて、プルリク作ったら実行して、エラーだったら落とすような感じです。

可用性が高いサービスが落ちたときを想定したテスト

新井:なるほど。では手元で実行しています、ということで完了で次に行きましょう。「DynamoDBやS3のように、可用性の高いサービスを使うとき、そのサービスが落ちたときを想定したテストはやはり書きますか?」。これは順番的には佐藤さんですが、いかがでしょう?

佐藤:正直そこまで意識できているかというと、微妙です。

和田:これは確かにあると思いますが、これが起きたということは、たぶんアプリケーションコードでどうにかできる世界ではないと思います。たぶん、いろいろなステークホルダーを巻き込んで立ち向かわなければならない話になってきそうなので、果たして想定するかどうかは確かにあります。

新井:想定して書いたテストコードに対するメリットみたいなところを、やはり考えてしまいますね。

加藤:そういう意味で言うと、たぶんみんなSDKまわりとかを聞いたときに、エラーハンドリングなどはしっかりと書いていて。想定できるエラー+unknownエラーを吐いた場合のハンドリングまでは書いて、でもそこをエラーを通るテストまでは、私は書いていません。

和田:それを書けるということはもう(笑)。

加藤:結局、そこを書いても正直あまりなので。エラーを拾ってログには残るようにして、適宜改善していけばいいぐらいに捉えています。

和田:そうですよね。想定できてない事態が発生しているのだと思うので、500が返ってきたときは問題で。ログを出して500で返すとしておけば、あとは追調査するのでいい気はします。

新井:確かにそのとおりですね。ユニットテストでは、僕はよくわからない系のエラーは全部拾うように書いていました。ログに出して、クライアントには500系で返すみたいな。ではこれも完了です。

ユニットテストのAWS APIテスト

「ユニットテストでは、AWS APIテストを行うという理解で合っていますか?」。

和田:あくまで僕たちの考えでは、という枕詞がつきますが、ユニットテストでは、基本的に通信は行わない方針です。Googleのガイドラインなどでもネットワークを使うか使わないかのガイドがあったと思いますが、ユニットテストでは基本的には外部と通信しない。その代わり、手元などで実行しやすくするのを方針としているはずなので、それにのっとったかたちで、ユニットテストでSDKで通信したりは今はしません。お二人も同じですか?

加藤:同じですね。ついでに、チャットに和田さんが言ったGoogleのテスト指標を貼っておきました。

和田:ありがとうございます。

加藤:ユニットテストはSmallだからという話ですよね。

和田:そうそう。Smallだから。

加藤:私も完全同意です。

Fakeテスト後の本番サービステスト

新井:ありがとうございます。ではそんな感じで、回答OK。先ほどのサービス、MinIOですね。ごめんなさい、言い間違えました。では最後、「Fakeのテストをやったあとに実際の本番サービスを使ってテストをしますか?」。これは加藤さんどうですか?

加藤:Fakeテストはあくまでインテグレーションテスト。先ほどのものでいうと、Mediumで捉えて、E2Eは別途やります。「例えば、ローカルDynamoDBを使ったテストをやってOKであっても、クラウドの DynamoDBをつかった同じテストをやるのかなどです」。同じテストではないです。あくまでFakeはインテグレーションテストで、その実際のDynamoDBを使うのはE2Eテストと捉えています。

テストコードの内容も違って、インテグレーションテストをやるときは、単純に1つのAPIを通すような感じのテストしか書かないことが私は多くて。対して、E2Eはわりとシナリオを作って、シナリオの中でDynamoDBをユーザーが当然触ることをフォーカスをして書く書き方をしています。

(次回につづく)