CastingONEにおけるフロントエンドテスト戦略について
はじめに
こんにちは、CastingONE開発部です。今回は CastingONE のフロントエンドチームでテストをどう取り組んでいるのかについてご紹介できればと思います。
CastingONE では、バックエンド領域ではテストをゴリゴリ書いているいるものの、一方でフロントエンド領域では書いたとしても関数の単体テスト程度で、それ以上のテストについてはほとんど取り組んでいませんでした。
そのようになっている原因としてはいくつか考えられます。
- フロントエンドテストに書き慣れていない
- フロントエンドをテストする文化がない
- フロントエンドテストの効用がわからない
などなど。他にも原因は色々あると思いますが、こういうケースは他の会社でも多いのかなと感じています。
こうした状況の中で、CastingONE ではフロントエンドテストについて徐々に取り組み始めました。そうなる過程で、意識したことやどういう形で取り組んでいるのか、などをご紹介できればと思い、これから取り組んでいきたいと考えている方にとってその一助となれば幸いです。
テストを書く目的
テストを書く上でチーム内でその目的についての共通認識を持つ必要があります。テストを書く目的、それは、プロダクトや組織がスケールしながらもデリバリー速度を落とさないような開発サイクルを維持するためだと考えています。つまりは品質を保証しつつ機能開発も進めていくということです。となると、手動テストには限界があり、やがてスケールしにくくなります。自動テストを書くことで、高速で安定したフィードバックを得ることができます。また、偽陽性や偽陰性が低いような適切なテストであれば、テストに高い信頼をおくことができ変更に対する心理的なハードルも下がります。ただ、テストコードも当然保守する必要があり、不適切なテストは負債になり得ます。そうしたことからテストの話になるとよく ROI の話になりますが、とにかく早くリリースすることが求められるプロジェクト初期は、 ROI を考慮してテストを書かない選択もありだと思います。テストを書くとしても「最低限のテストで最大の効能が得られるように」ということは意識する必要があるでしょう。
フロントエンド領域のテストに関しては、フロントエンド領域の品質を保証するということが目的になると思います。具体的には、画面遷移・描画から、操作し、APIに対するリクエスト送信後までが責任範囲となり、その範囲内で適切なテストを書いていくことになります。
テスト戦略
冒頭でも述べましたが、CastingONE フロントエンドチームのテストに対する状況について改めてお話すると、導入前は以下のような状況でした。
- フロントエンドのテストを書いた経験はほとんどない
- 関数の単体テストは書いたことあるが、UI に関するテストは書いたことない
- Storybook は書いた経験がある
そういったチーム状況下で最初から全てのテストを導入するというのは非現実的な案で、仮に導入したとしても運用が回らないでしょう。導入と、その先の運用を見据えた時に以下の2点を意識して方針を検討しました。
- 最初から手広くやりすぎず、小さくスタートする
- テストに慣れてきたら拡充していく
フロントエンドの種類は色々あるため初めからあれもこれもやりたいとなりがちです。その逸る気持ちを抑え、運用を意識した戦略にするのが好ましいです。また、テストを書き慣れてない初めのうちはテストを書くのに時間を要します。そのためスプリント内に終わらなくなりテストに対してネガティブな感情も抱くこともあるでしょう。しかし、テストを書き慣れてくれば書くスピードも上がり、質・量ともに高いテストを書くことが出来ます。そのタイミングで、今まで手をつけていなかったテストに取り組んでいくのが良いと思われます。
詳細な方針については、フロントエンドテストの種類も整理しながらお話したいと思います。テストの種類については個々人や切り口によって分類方法が分かれるところですので、これが絶対ということではありません。
静的テスト
ESlint による静的検証や、TypeScript による静的型チェックなどを実施するテストです。静的テストは現在のフロントエンド開発ではほとんど当たり前となっており、プロジェクト初期から設定されているケースが多いと思います。VSCode ユーザーであれば保存時に自動で走るように設定されていれば、ほとんど意識せず静的テストの恩恵を得ることが可能です。
弊チームでも導入しており、CIにも組み込んでいるため静的テストが落ちている PR はマージできないような運用としています。
単体テスト
単体の関数あるいはコンポーネントに対して、振る舞いをテストしたり、アサーションテストやスナップショットテストを実行しています。単体のコンポーネントに関しては、Storybook を利用すればコンポーネントカタログとしても機能し、コンポーネント単位で挙動を確認することが出来ます。
単体テストに関しては、関数のみを必須でテストしてもらうようにしています。コンポーネントの単体テストは ROI が見合わないと判断できる場合もあり、一旦は任意と置いています。ただ、コンポーネントもスナップショットテストだけは実行してもらうようなルールとしています。そうした理由としては、
- スナップショットを撮るだけなら実装コストは高くない
- 後述する VRT テストでは検知できないデグレを検知できる
といった判断からです。
結合テスト
結合テストは、内部で API へのリクエストするようなコンポーネントが対象になるケースが多いです。ここでは、API は Mock Service Worker でスタブやモックを用意して実行されます。期待する値がリクエストされているかであったり、送信後に期待する振る舞いをしているか等の観点でテストを実装します。
結合テストは、Testing Library を結構書くことが求められ、Jest や jest-dom 周りでハマったりするので、最初は結構工数がかかってきます。そのため、現段階では任意としています。しかし、Testing Trophy の考えに照らし合わせれば、結合テストが最も ROI が高いとされています。そのため、結合テストは書いた方が良いだろうと考えており、今後拡充していく予定です。
E2E テスト
本来的な E2E テストはフロントエンドから実際のバックエンド(実際のサーバー、実際のDB)に対して実施します。テスト対象が広範囲なため、フレーキーかつフィードバックまでの時間を要する等のデメリットもありますが、ユーザーの実際の動きを保証することが出来ます。
Playwright などで E2E テストを書くことを検討しましたが、既にCastingONE の QA チームで Autify を利用した E2E テストが進められているため、フロントエンドチームでは見送ることにしています。
CastingONE の QA チームの取り組みについては以下の記事をご覧ください。
VRT テスト
VRT は Visual Regression Test のことで、実際の画面を元にリグレッションテストを実施します。よくあるのは、Storybook を利用した方法で、Story のスクリーンショットを比較し差分検知することで、意図せぬ変更が起こっていないかを視覚的に確認することが出来ます。
弊チームで導入している VRT は、Storybook + storycap + reg-suit を利用したやり方で、実装者が新しく Story を作成したら自動的にその Story の VRT テストが走るようになっています。そのため、Story の作成は最低限してもらう運用となっています。Story は元々作っているというメンバーが多く、そこまで負担にならないだろうとの判断からです。また、現状の運用は、差分があったとしても Pull Request に自動でコメントを出すという程度にとどめており、厳格にチェックしているわけではありません。これはまだプロジェクトが十分に成熟しているわけではなく、UI 変更が頻繁に起こりうるためです。
最後に
今まで述べてきたような方針でスタートしましたが、これが正解かどうかは現時点ではわかりません。今後、実際に運用しながらメンバーの声を拾ったりして随時見直しを図りながら、より良いテスト戦略を模索していきたいと思います。
弊社では、フロントエンドテストを推進してくれるメンバーを大募集しています。カジュアル面談もやってますので、お気軽にご連絡ください!
Discussion