バックエンドのUTを厚くしてテストサイズで分類した話
前回の投稿からかなり間が空いてしまいました💦
ユートニックの衛藤です。今回はテストについてです!
一番最初の投稿で、ユートニックのシステム全体像について紹介しました。このシステムを少人数で支えていくためのテスト方針や、テスト効率化についてお話します。
ユートニックで提供しているアプリ・機能
ユートニックでは2023年6月現在、25を超えるアプリをWeb, iOS, Androidで提供しています。
https://about.utoniq.com/app-list
これらのアプリでは、以下の通り様々な機能が提供されていますが、これらのアプリが保有する機能すべてを裏で支えているバックエンドでは、テストをしっかりと書いていく方針としています。
バックエンドのテスト方針
上述の通り、ユートニックでは多くの機能を実装しており、現在も機能拡充を行っており、かなりのスピードでリリースされていっています。
そのため、バックエンドではテスト実装も必須にしており、なるべく追加実装によるデグレや機能テストの工数を減らすように努めています。(言語はTypescript + jestを利用)
また、テストピラミッドという考え方に従い、ユニットテストを厚めに実装する方針としています。テストピラミッドについては以下のAndroid公式サイトが参考になるので貼っておきます。
テスト実装方針としては、C1カバレッジ以上推奨、全体のカバレッジは60%以下にならないように気をつけています。
現在はほぼユニットテストのみで全体カバレッジが76%程度まで上がってきています。Integrationテスト(GraphQL API結合レベル)は任意です。
CIでのチェック
バックエンドのデプロイ前はCIチェック必須とし、PRごとにユニットテストのみを自動で実行しています。
以前はユニットテスト + Integrationテストをそれぞれ実行していましたが、Integrationテストが1時間近くかかることもあり、現在はローカル環境で実行したいときに任意でチェックすることにしています。
しかし、それでもユニットテストの実行で時間がかかるようになってきていました。ユニットテストだけでも数千ケースあり、1日何本も出しているPRで毎度回ることになるため、徐々に効率が下がるようになってきたのです。
テストサイズで分ける
また、このタイミングでjestのメモリリークに悩まされはじめ、何か対策を取らざるを得ない状況となってきました。
そこで、以前からやりたいまま手付かずでいたテストサイズでの分類を取り入れることにしました。テストサイズの分類はGoogle Testing Blogで細かく紹介されていますが、Small Test・Medium Test・Large Testの区分けがあり、特にSmall Testではネットワークアクセスやファイルシステムとは一切やりとりをしない、などの取り決めがあります。
ユートニックでは、ここまで細かくは定めていませんが、おおまかに取り決めて分類することにしました。
Small Test
単体ファイルに閉じたテスト
DBアクセス、外部ネットワーク接続がないテスト(DBはテストダブル必須)
実行時間が各ファイルおおよそ1秒以内に終わる
Medium Test
DB接続や外部にアクセスが必要なテスト(※ なお、DB接続はFirebaseのFirestore Emulatorを利用しています)
2ファイル以上での依存があるテスト
実行時間が各ファイルおおよそ数秒〜数十秒以内に終わる
Large Test
GraphQL 結合レベルのIntegrationテストが該当
実行時間の制限はなし
まずは上記に従って、既存のテストファイル名を一気にリネームし、CI実行のトリガーをファイル名マッチングに変えていきます。
実は、細かい部分でSmall Testなのにどこかでネットワーク通信してしまっている、などのまだまだ改善の余地がありますが、まずは気にせず一気に作業することが大事かなと思います。(新規ファイルから順次・・とか、徐々に徐々に・・・というのはおそらく途中でぐちゃぐちゃになります)
テストの実装をざっと確認しファイル名を以下のように変えていきます。
// Small Testに該当する場合
someTestFile1.test.ts => someTestFile1.small.test.ts
// Medium Testに該当する場合
someTestFile2.test.ts => someTestFile2.medium.test.ts
CIでのテスト開始スクリプトでは、ファイル名末尾のsmall.test.ts, medium.test.tsをキャッチするように変えるだけです。そして、CIの実行をSmall TestとMedium Testで分割し、それぞれ並列で実行させます。
// これまで
yarn test --testPathPattern=.*\\.test\\.ts
// Small Testの実行
yarn test --testPathPattern=.*\\.small\\.test\\.ts
// Medium Testの実行
yarn test --testPathPattern=.*\\.medium\\.test\\.ts
今回の変更により、以前は数千ケースが直列に走り30分前後程度かかっていたCIのトータル実行時間が、Small Testは数分以内、Medium Testは10-15分以内に終わるようになっています。この2つのテストはそれぞれ並列で実行されるため、全体での実行時間は最大15分程度まで短縮されました。
おそらくjestのメモリ逼迫によるパフォーマンス劣化も少しはましになったのかと思われます。
今後はSmall Testの精査と、Medium Testをより短くする取り組みをしていきたいと考えています。
それでは、また次回に!
ありがとうございました!