はじめに
こんにちは! 技術本部 Bill One Engineering Unit の市川です。 Bill One に Join してから早いもので 1 年が経ちました 🌸
今回は 2022 年 9 月ごろから 3 カ月かけて実施した、 Bill One の React フロントエンドにおける Visual Regression Testing (以下、VRT) 導入について、 フロントエンドチームの取り組みや導入時の進め方を紹介していきます。
なお、本記事は【Bill One 開発 Unit ブログリレー】という連載記事のひとつです。
目次
VRT 導入の背景
フロントエンドチームの紹介
VRT についての話を進める前に、Bill One における開発体制について簡単に説明します。
Bill One の開発組織は複数のフィーチャーチームから構成されています。 また、エンジニアにはフロントエンド・バックエンド担当といった役割分担はなく、 各チーム・タスクの状況に応じてフロントエンド・バックエンド両方の開発を行っています。
一方、フィーチャーチームとは別に、横串活動・横串チームというものが存在します。 横串チームとは、各フィーチャーチームだけでは扱いきれないような、開発組織全体の課題に取り組む有志チームです。 前回の記事では IaC 改善チームが紹介されていましたね。
Bill One におけるフロントエンドチームはこの横串チームの一つです。 フロントエンドチームは、「開発スピードを維持し、継続的にユーザーに価値を提供し続ける」体制の整備を目的として活動しています。 今回の VRT 導入はこの横串チームの活動として実施したものです。
テスト方針の改定
これまで Bill One では、 Pure Function や Custom Hooks に対する Unit Test は記述されていたものの、 UI ・ビジュアル観点のテストはほとんど整備されていない状況でした。 そのため、機能開発に伴う予期せぬ UI 差分や表示崩れがリリースされてしまい、場合によってはそれが不具合につながるケースもありました。 当然のことですが不具合が発生してしまうと修正対応に時間を要することになり、機能開発のスピードに影響を与えてしまいます。
当時、フロントエンドチームでは、開発者体験の改善を目的としたリファクタリングや UI ライブラリの移行を検討していました。 しかし、UI 差分や表示崩れを自動検知する仕組みがないままリファクタリングを行うことはリスクが高すぎます。 まずはそうした作業を安全に行うための環境を整えるよう、フロントエンドのテスト方針を見直す必要がありました。
方針の見直しにあたって Testing Trophy というフロントエンドのテスト戦略ガイドを参考にしました。
今回導入した VRT は UI のスナップショット画像を撮り、前回テスト時のものと比較することで UI 差分を検出するテスト手法の一つです。 Testing Trophy で表されるテスト種別のうち、対応コストに対するテスト効果が最も高いとされる Integration Test に分類されます。 既に対応されている Static Test1、 Unit Test に加えて Integration Test である VRT を実施することをテスト方針として定め、 予期せぬ UI 差分の混入を未然に防ぐ環境を整えることが決まりました。
Testing Trophy についての詳細は、以下の Bill One Entry 秋山さんの記事にて解説されています。 ぜひそちらも併せてご覧ください。
Storybook と Chromatic
Storybook は UI Component をアプリケーションから分離独立させて開発・テストするためのツールです。 UI Component のカタログとしても活用されていますね。 近年のフロントエンド開発においては、この Storybook を用いた Component Driven Development が広まっています。
Storybook を用いた VRT の仕組みを提供する SaaS はいくつか存在します。 その中でも、Storybook との親和性が最も高く2、レビューやコミュニケーションの機能が充実している Chromatic を採用しました。
余談ですが、この 4 月に Storybook v7 がリリースされましたね 🎉 Bill One フロントエンドのビルドツールには Vite を用いているので、Vite の Native サポートは待望の Update でした。
Bill One における VRT 導入の進め方
お待たせしました。ここからが本題です。
Storybook と Chromatic を導入したからといって、それだけで VRT が実現できるわけではありません。 Chromatic 上で UI 差分を検知するためには、テスト対象となる Component の Story 3 を用意する必要があります。 VRT 導入の中でも最も手間のかかる作業なのですが、今回、どのように Story 追加を進めていったのか順を追って話していきます。
1. VRT 対象 Component の絞り込みとゴール設定
Bill One フロントエンドの Component 総数はすでに 1000 件4を超えていました。 日々の開発業務がある中、有志による横串活動としてそれら全ての Story を用意するのは無理があります。 まずは、VRT の対象とする Component を絞り込む必要がありました。
先にも述べたとおり、今回の VRT 導入の目的は UI Component カタログを作ることではなく、 予期せぬ UI 差分を検知してリリースへの混入を未然に防げる状態を作ることにあります。 つまり、ボタンやラベルといった小さな Component より、 画面全体をカバーする Page Component 5に対して VRT を実施するほうが効果的です。 VRT 導入開始時点で存在する Page Component は 130 件。 それら全てに対する Component Story を 3 カ月で用意し切ることをゴールとしました。
2. Mock Service Worker を用いた API Mock
さて、Page Component に対する Story 追加を行うことになったわけですが、 各画面にて行われる API 呼び出しをどう扱うのかという課題があります。 VRT が行われる Chromatic・Storybook 環境から実際のバックエンドサービスに対して API 呼び出しするわけにはいかないからです。
以前は、 Page Component に対する Story を記述する場合、 Container/Presentational Component パターン を取ることが一般的でした6。 このパターンは Component としての責務を、 アプリケーションのロジックや API 呼び出しを行う Container Component、 UI 表示を行う Presentational Component に分離することで、 Component の再利用性やテスト容易性を高めるためのものです。 Presentational Component から API を呼び出すことがないので、バックエンドサービスを意識することなく Story を記述できます。 Bill One の Page Component はこのパターンを採っておらず、限られた工数の中で Component の分割・リファクタリングを実施するのは現実的ではありませんでした。
そこで今回採用したのが、近年よく用いられている Mock Service Worker (MSW) を用いて API を Mock し Story を記述する手法です。
MSW はブラウザの Service Worker の仕組みを利用することで、 API 呼び出しをネットワークレベルでインターセプトして Mock するものです。 Mock 対象の API Path とレスポンスを書くだけで、容易に API Mock を実現できます。
rest.get<Request, PathParameter, Response>( "https://api.example.com/user", (req, res, ctx) => { return res(ctx.json({ username: "Bill One 太郎" })); } );
この仕組みを Storybook 上で実現する Storybook Addon を用いることで、既存の Page Component に手を加えることなく Story を作成できます。 今回は Story の整備とあわせて MSW を用いた API Mock の整備も進めていくこととしました。
3. 作業分担と勉強会の実施
背景にて説明したとおり、Bill One フロントエンドの開発には Bill One の全エンジニアが関わります。 今回の VRT 導入を横串チームであるフロントエンドチームが単独で行ってしまうと、 この先も Page Component が増える度にフロントエンドチームが Story 整備を行う状況に陥りかねません。 もしそうなった場合、フロントエンドチームが開発のボトルネックになることも予想されます。
各フィーチャーチームで自律的に Story と API Mock を追加・メンテナンスしていくためには、 VRT 導入当初から Storybook を用いた開発手法に慣れてもらう必要があります。 そのため、Page Component に対する Story 追加作業は各フィーチャーチームに分担してもらうことにしました。
フロントエンドテスト方針の改定や VRT 導入の意図や目的については、 横串活動の成果発表会やラーニングセッション7を通じて全エンジニアに周知しました。 加えて、Storybook の使い方や Story・API Mock の書き方についての勉強会を実施することで、 各チームメンバーがスムーズに Story 追加作業を行える状態を整えました。
4. ゴール達成へ向けた進捗管理
各チームへ作業依頼したものの、どのチームも日々の開発業務で多忙です。 依頼投げっぱなしでは忘れ去られてしまいます。 そのため、毎週進捗グラフを Slack にて共有することで VRT に対する関心を維持するよう務めました。 また、作業する上での疑問点や不明点があれば、その都度相談を受けていました。
期限ギリギリの追い込み作業もありつつ、なんとか当初のゴール設定通りに Story 追加対応が完了しました 🎉 この場を借りて改めて、対応してくれたメンバーの皆に感謝します。ありがとうございました!
VRT 導入後の現状と今後の取り組み
VRT 導入から半年ほど経過した現在の状況と、今後の取り組みについて話していきます。
導入効果
VRT を導入したことで、フロントエンド実装の Pull Request (PR) に対して自動で VRT を実行し、 Chromatic の UI Tests 機能を用いて UI 差分を検出・レビューできる状態となりました。 この UI Tests の確認・レビューを PR マージ時に必須とすることで、予期せぬ UI 差分のリリース混入を未然に防いでいます。 実際にリグレッションが防止できたケースがいくつもあり、導入効果を実感しています。
活用状況
VRT 導入後に追加された新規 Page Component 12 件のうち 11 件には Story が追加されています。 カバレッジ 100% とはなりませんでしたが、VRT 対応のテスト方針はおおよそ守られているようです。
Page 以外のより細かい粒度の Component に対する Story も増加しており、 Storybook を用いた開発手法が徐々に浸透しつつあります。 また、Storybook を用いた Interaction Test 8 も記述され始めています。 これらの取り組みは、フロントエンドチームが強く働きかけを行ったわけではなく、 各チームでフロントエンド開発やソフトウェア品質について感度の高いメンバーが自発的に取り組んでくれたものです。 Bill One 開発組織の力強さを改めて感じます。
課題
一方で、目下課題になっているのが Chromatic 利用コストの増加です。 余分な VRT 実行を回避するため、テスト対象の Story を絞り込んだり9、 TurboSnap 機能を有効化しているものの、 Page Component Story の増加やそもそもの開発の活発さも相まって増加傾向にあります。 今後より一層 VRT・Chromatic を活用していく上での障壁とならないよう、 Component や Module の依存最適化などを通じて TurboSnap 機能の適用範囲を拡大し、コストの適正化に取り組む必要があります。
今後の取り組み
Page Component に対する VRT は実現できたわけですが、Component の粒度が大きいためページ内の全ての挙動を網羅することは困難です。 そのため、完全に UI 差分起因のリグレッションを防ぎきれているわけではありません。 今後は Page Component に加えて、より細かい粒度の Component に対する VRT を実施していく必要があります。 フロントエンドの品質と、対応コスト・Chromatic 利用コストとのバランスを取りつつ、VRT 対象を拡大していく予定です。
予期せぬ UI 差分の混入を未然に防ぐ環境が整ったことで、 フロントエンド全体に影響するような大きな変更を加えることのリスクが低減しました。 今後、フロントエンドチームでは UI ライブラリの刷新をはじめとしたリファクタリングを通じてフロントエンド開発体験を改善し、Bill One 開発をより一層加速させていきます 🚀🚀🚀
おわりに
今回は、Bill One での VRT 導入の取り組みについて紹介しました。 VRT 導入の進め方の一つとして、とりわけ既存の大規模プロジェクトへの導入時に参考にしていただけると幸いです。
Bill One では一緒に働く仲間を募集しています! 詳しくはこちらのリンクから採用情報をご確認ください。
- ESLint や tsc による静的解析。↩
- Chromatic は Storybook の Maintainer によって開発・提供されているサービス。↩
- Story とは Storybook 上で UI Component を表示したりテストするために必要なコードのこと。↩
- Test、Story、Custom Hooks を除いた拡張子 .tsx を持つファイルの数。↩
- ひとつのページを構成する、最も大きな粒度の Component のこと。↩
-
React Hooks 登場以後は Component を分割せずとも関心の分離が可能となったため、 Component を分離するメリットは薄れている。 Container/Presentational Component パターンが提唱された記事 Presentational and Container Components でも、Hooks を活用すればよいとのコメントが追記されている。
↩ -
小さなことから大きなことまで、今週学んだ主に技術的なことを共有する場。 参考: 新サービスのオンボーディングを成功させるためにエンジニアが取り組んでいること
↩ - ユーザーによる UI 操作 (インタラクション) に伴う UI の挙動をテストするもの。 Integration Test に分類される。↩
-
Story の VRT 除外設定 の設定をデフォルトで有効化し、Page Component ほか VRT 実行したい Component Story 側で明示的に VRT を有効化するようにしている。
↩