Classiのフロントエンドエキスパートチームのlacolacoです。最近AngularのプロジェクトでStorybookをベースにしたUIコンポーネントの画像回帰テストをはじめたので、この記事ではそのなかで学んだことを共有します。
画像回帰テスト
画像回帰テスト(visual regression testing)は、UIの見た目のバグを防ぐためのテストです。見た目はユニットテストのようにコードでテストするのが難しいため、画像回帰テストではその名の通りテストに画像を使用します。実際に描画されたUIを画像としてキャプチャして、ソースコードの変更前後での画像の差分から「見た目が変わっていないか」をテストできます。
最近では画像回帰テストをサポートするツールやSaaSなどさまざま出てきていますが、画像回帰テストをはじめるにあたっての問いはシンプルに次の2つだけです。
- どのように画像をキャプチャするか
- どのように画像を比較するか
どのように画像をキャプチャするか
最初の問いは、そもそもどのようにUIを画像化するかということですが、今回はStorybookと、そのプラグインであるreg-viz/storycapを選択しました。
Classiでは社内で利用するAngularのUIコンポーネントをライブラリとして開発しています。そのライブラリにはもともとドキュメンテーションを目的としてStorybookを導入していました。すでに見た目の確認のためのStoryを定義しているので、これをそのまま画像にできれば一石二鳥です。
別の選択肢として、Cypressをベースにすることも検討しました。Cypressには画像回帰テストをまるごとサポートするプラグインがあったのですが、Cypressを新規に導入する手間や、すでにあるStorybook資産を再利用したいことなどを考えた結果、最終的にStorycapを選びました。
どのように画像を比較するか
次の問いが、キャプチャした画像をどのように比較して回帰テストするかということです。極端にいえば2枚の画像を1ピクセルずつ比較して差異を探せばよさそうですが、実際はそこまで単純ではありません。ブラウザのスクリーンショットという手段で画像化する過程で、わずかな誤差が生じる可能性があります。差分があることだけわかっても「どの部分がどう変わったか」を人の目で見つけるのは大変なので、テスト結果の視覚的な支援も重要です。
というような観点で検討した結果、今回はreg-viz/reg-cliを使用しました。reg-cliは機能面で要求を満たしていましたし、Storycapと同じ開発元ということで親和性という点でも安心して使えました。
小さくはじめるための工夫
今回はすでにあるプロジェクトへ後から画像回帰テストを導入し、有効性を検証することが第一の目的であったため、なるべく最小限の変化ではじめられることを重視しました。そのためにいくつかの工夫をしています。
対象を絞る
最初からすべてのコンポーネントに対して有効にせず、仕様が比較的安定している限られたコンポーネントだけを対象にしました。対象を絞ること自体はStorycapの skip
オプションや exclude
オプションなどを使用して簡単に実現できました。
今後対象コンポーネントを広げていく上でも、すべてのStoryを回帰テスト対象にはしないだろうと考えています。対象が増えるごとにキャプチャの時間的コストが開発体験上で問題になることが予想できます。コスパのバランスを考えて適用範囲を広げていく必要がありそうです。
ローカルでスナップショットを更新する
今回の導入では、開発者がUIを変更したらローカルでキャプチャした画像を新たなスナップショットとしてGit pushします。巷の画像回帰テストツールでは、スナップショット画像はGit管理せずクラウドストレージに保存することが多いようです。これは画像回帰テストの実行には時間がかかることが主な理由のようです。画像回帰テストはCI環境だけで実行し、結果をクラウドストレージに保存することでローカルの開発者体験を損なわないようにされています。
確かに大規模に画像回帰テストを導入するとなるとその部分がボトルネックになることは想像できますが、今回は小さくはじめることを目的に簡略化しました。本格化するにあたっては、Storycapやreg-cliと同じ開発元のreg-suitやSaaSのPercyなど、このあたりのツールを検討する見込みです。
ちなみに、ローカルとCIでは環境が違って同じChromeでも描画結果が変わるため、StorycapはDocker上で実行するようにしました。特にフォントレンダリングで差が出ます。最初はどうにかしてやろうと闘いましたが、途中で徒労だと悟ってDockerに切り替えました。これも実際にやってみたらあっという間だったので、はじめからこうしておけばよかったと反省しました。
導入後の学び
まだ小規模な導入なので著しい変化はありませんが、それでも適用対象のソースコードに関しては画像回帰テストがされていることでリファクタリングへの安心感が高まりました。社内で横断的に使われるので、ライブラリのアップデートが意図しない破壊的変更を含まないことを検証する仕組みとして、やはりとても重要だと感じています。
既存のStorybookの運用についても、画像回帰テストしたいパターンのStoryが作られていないことに気づいたり、コンポーネントの仕様上の問題に気づいたりと、新たな視点でコンポーネントを見ることで改善の種が見つかることもありました。
まとめ
この記事の内容自体は特に目新しいものではないと思います。画像回帰テストは普及してきつつありますが、しかし「知っているし導入したいと思ってるができていない」という状態のプロジェクトがまだまだ多いと感じています。思ってたより簡単だったり逆に大変だったり、実際にやってみないとわからないことは多いので、この記事がこれからはじめたいと思ってる人の後押しになれば幸いです。 ここでは語れていないこともたくさんあるので、もっと詳しい話をしながら意見交換していただける方は lacolaco 宛にDMなどいただけると喜びます。 それではまた!