こんにちは。制作部の本多です。
最近、外部 APIをコールしレスポンスをDBに保存するLambda関数を開発したのですが、その際に使用したTypeScriptが非常に使いやすく、有意義な開発ができました。
TypeScriptの機能で特にありがたかったのが、typeでデータ構造を定義できる点です。
APIのリクエストやレスポンスをtypeとして定義しておけば、レスポンスの中身を処理したい時にIDEが補完してレスポンスに存在する値を表示してくれますし、リクエストデータを作成したい時も、型を付与して変数を宣言すれば、どんな値をリクエストに含めればいいのかが一目でわかります。
逆に存在しない値にアクセスしようとした際には、IDEが警告を発してくれますし、数値を入れなければならないところに文字列を入れようとしている時にも、IDEがこれはおかしいと教えてくれます。
また、今回はテストフレームワークにJestを使いました。
今回の開発では、処理の中でAPIコールとDBへのアクセスを行なっています。
先方から外部APIのOpenAPIドキュメントをいただいたため、開発の初めにDockerコンテナでAPIのモックサーバーを立てていました。
■OpenAPI形式のドキュメントからモックサーバーを作成するprism
https://stoplight.io/open-source/prism
ただし、上記のツールで自動生成されるモックサーバーは、毎回同じ値を返します。
モックサーバーが返す値を動的に生成しようとすると、ドキュメントからの自動生成では無理で、自分でコードを書いて実装する必要があります。
また、外部APIが返す値の一つに、たまにレスポンスから抜けるもの(返ってこないもの)や、DBの内部を見て、レスポンスの値と重複するレコードがあるなら次の処理に渡したくないものなどがありました。
それらの値が返ってきた場合でも、正常に処理が行われるかチェックしたいです。
そのため、テストでは上記で自動生成したモックサーバーを使うのではなく、処理の中にあるAPIコールをモックすることで、複数パターンのレスポンスに対応できているかをチェックすることにしました。
レスポンスのデータは手作業で作らなければなりませんが、レスポンスデータを作成する関数を作ってしまえば、引数で値を渡すことで、ある程度動的にレスポンスデータを作成することができます。
そして、APIコールのモックはJestを使うことで数行で記述できます。
Jestでデフォルト値設定済みのaxiosインスタンスに対するMockテスト
APIコールにはaxiosを使っています。Jestではライブラリ自体をモックすることができるため、コードの中にあるaxios.post()の返り値を、自分が作成したレスポンスデータにすることができます。
ただ、網羅的なテストをするためには適切にレスポンスデータを生成しなければなりません。
もう一つのDBアクセスについては、テスト用のDBを作成し、そのDBに対して実際にアクセスしてテストしました。
ただし、一つのテストが終わったらDB内のデータを全て削除し、まっさらな状態にした後で次のテストを開始したいですが、そのような機能はJestには含まれていないようでしたので、DBのリフレッシュは自前で実装する必要がありました。
TypeScript な Node.js + Jest でテスト毎に DB(MySQL) をリセットする関数
また、今回はORMを使わず、素のSQLを記述してDBアクセスを行なっていたため、LaravelにあるassertDatabaseHasのような便利なメソッドを使うことができませんでした。
そのため、DBにちゃんと保存されたか〜?という確認をしたい時は、その都度SELECT文を発行しなければなりません。。。
(個人的にはこれが一番たるかったです)
ただ、結果的にはそれもSQLを書く練習になってよかったと思います。
(特に、テストとは関係ないところですが、INSERT IGNORE や REPLACEなどを知ることができたのはよかったです)
丁寧にテストを書いてよかったこと
・バグが少ない(自分比)コードが書けたこと
・不具合箇所の特定が早くなった(気がする)こと
STG環境で不具合が起きたとしても、ここまではテストしているから、これはこの箇所のせいではない、ということが分かるので、原因箇所の絞り込みに役立ちます。
一番大事なことは、テストケースを列挙することで、考慮もれのパターンを潰せることかと思います。
今回、TypeScriptでLambda開発を行うということで、環境構築から開発ツールの選択、設計、テストまで行いました。
プロジェクト自体は大きな問題もなく進んでいるかと思います。
ただ、設計については少し不安なところがあるので、もっと経験を積んで保守性の高い設計をできるようになりたいです。(例:通常実行とリトライ実行で同じlambda関数を使いまわしている。←の実現のために、それぞれの処理をUseCaseの形で作成し、インプットデータの構造によって通常とリトライを出し分けている。ストラテジーパターンではない(断言))
テストを書くのは手間ですが、利点も多いです。今後も時間を見つけてテストを書いていきたいと思います。