READYFOR Tech Blog

READYFOR のエンジニアブログ

Integration Testing を改善!Testing Trophy を整形してCI実行速度を5倍に

この記事は「READYFOR Advent Calendar 2021」4日目の記事です。 Calendar for READYFOR Advent Calendar 2021 | Advent Calendar 2021 - Qiita

フロントエンドエンジニアの吉井(@Takepepe)です。業務委託で READYFOR さんのお手伝いさせていただくようになってから、早いもので1年半が過ぎました。この期間中、3つの Next.js Application 立ち上げ・リリースに携わらせていただきました。

Cypress x BDD

現在携わらせていただいているのが「フロントエンド基盤SQD」という、READYFORプロダクト全般のフロントエンド課題をシューティングするスクワッドです。このスクワッドでの活動において、私が9月〜11月に取り組んだ課題が「テスト方針の見直し」です。

3つの Next.js Application はテスト方針として、BDDを基本とした Cypress Integration Test に力を入れてきました。Cypress は Next.js 公式ドキュメントでも紹介されているテストランナーで、End-to-End (E2E) や Integration Testing を行うツールです。

nextjs.org

Cypress は GUI が充実していて、ブラウザでテストケースの挙動を確認しながらテストを書くことが出来ます。開発体験の良さ・BDD との相性の良さから、Cypress でのテストケースをこれまで充実させてきました。

テストの実行コストが課題に

しかし、立ち上がり初期はうまくいきましたが、テストケースが充実するにつれ「Flaky」なテストケースが目立つようになりました(Flaky テストとは、実行結果が不安定なテストの事です)。ローカル開発環境では成功するものの、いざ CI で実行すると、ランダムに失敗するという不安定なものです。

また Cypress に限らずですが、headless browser のテスト実行時間は比較的長く、高価です。そのためコミットフックで実行されるのでは、あまり効率がよくありません。ある Application では特定ブランチのテストを定刻実行するようなパイプラインを組み・失敗したら Slack に通知する様な仕組みで運用していました。

PR 毎に CI でテストが回っていない状態は、問題発覚が遅れるため好ましい状態ではありません。これらの課題に対し、取り組みをはじめました。

Testing Trophy を整形した

Next.js 公式ドキュメントにもある通り、フロントエンドのテストツールは多くの選択肢があります。「Testing Library」は Cypress のような headless browser と同様に、UI の Integration Test を書くことができるツールです。単体テストでお馴染みの jest と組み合わせて使います。

今回の取り組みは、Testing Library の作者である Kent C. Dodds 氏が提唱する「Testing Trophy」に準ずるものとしました。Integration Testing の層を手厚くすることで、より効果的なテスト戦略とすることが可能と説かれています。

kentcdodds.com

3つの Next.js Application は Cypress の Integration Test に力を入れていたこともあり Testing Library の Integration Test はほとんど無い状態でした。Trophy の形状でいうと「逆三角形型」だったものを「寸胴型」に整形することを目標としました(Cypress は E2E寄り)。

3つの Application のうち最も Flaky に悩まされているものを選び、集中的に改善。といっても、基本的には「jest + Testing Library で担保可能なケースは、Cypress から委譲する」という地道な作業です。今回取り組んだ Application に限っては、画面を横断する様な機能がほぼ無いこともあり、ほとんどのテストケースを委譲することが出来ました。

結果

次のテーブルは、テストケースを複数の PR にわけて委譲していった結果です。合計で 9分かかっていたテストが、1分半に圧縮、5.4倍の改善結果となりました(cypress 実行時間が 473秒減、jest 実行時間が 32秒増)。 委譲しているだけですので、当然テストケース数に増減はありません。

PR jest time cypress time total time improvement
#545 4.664 s 536.70s. 541.364s. 1
#659 86.97s. 369.00s. 455.97s. 1.187
#668 90.77s. 284.89s. 375.66s. 1.441
#675 36.16s. 134.67s. 170.83s. 3.169
#676 38.96s. 90.66s. 129.62s. 4.176
#677 36.18s. 63.01s. 99.19s. 5.457

PRの #668 -> #675 のjest 実行時間が大幅に減っているのは、ボトルネックになっていた箇所を改善した成果です。

ボトルネックになっていた箇所

当該プロダクトでは react-virtualized-auto-sizer というライブラリを使っており、children の引数に画面の幅・高さが与えられます。このライブラリは jest 実行時に mock を使用する必要があるため、以下の様に大まかな width, height を与えていました。

jest.mock(
  "react-virtualized-auto-sizer",
  () =>
    ({ children }) =>
      children({ height: 2160, width: 3840 })
);

このエリアサイズが大きければ大きいほど、実行時のレンダリング速度に影響します。そこで、この値を小さくするだけで、上記の様な改善を施すことができました。

jest.mock(
  "react-virtualized-auto-sizer",
  () =>
    ({ children }) =>
      children({ height: 480, width: 480 })
);

まとめ

実行時間短縮だけでなく、Flaky だったテストケースも安定して成功する様になりました。この件に関しては「特定要素が表示されるまで待機しアサーションする」というテストの書き方を変更したこともひとつの要因です。

Cypress に限らず jest も、この様なボトルネック改善で高速化を実現することができるので、取り組みがいのある分野だと今回感じました。今回の成果をもとに、READYFOR プロダクト全般のテスト改善を引き続き継続していきたいと思います。