BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

ビジュアルリグレッションテストのツールを導入するまでの意思決定プロセス

はじめに

この記事はBASE Advent Calendar 2021の15日目の記事です。

BASE株式会社 Owners Experience Frontend チームのパンダ(@Panda_Program)です。

2021年の5月に入社してから、アサインされるプロジェクトの仕事以外に社内 UI コンポーネントライブラリ「BBQ」のメンテナンスに取り組んでいました。 その中でも特に Storybook 周りの整理をする過程、Storybook の v5 から v6 へのバージョンアップとその自動化のプロセスを以下の記事にまとめました。

本記事はこの続きにあたります。

BASE の社内 UI コンポーネントライブラリ開発において、ここまでは Storybook というツールをしっかり使うということを目的としていました。しかし、ここからは Storybook の力を引き出して開発者にその恩恵を感じて貰い、DX を向上させるところまで進めたいと考えています。

本記事では、現状の BBQ の課題を解決しつつ、Storybook をフル活用するために Chromatic の利用を検討し、社内で議論して実際に使った後、導入に至った過程を紹介します。

このため、本記事は「BBQ の課題 → 課題の解決策の検討 → ツールの選定 → ツール導入後の開発フローの変更の紹介」という構成を取ります。なお、Chromatic のインストール方法や具体的な使い方は本記事では扱わず、別の記事で紹介しています。

社内UIコンポーネントライブラリ「BBQ」の課題

BBQ とは何か

BBQ とは、BASE で利用している社内用の UI コンポーネントライブラリです。 Vue2 と TypeScript、scss で記述しており、Storybook で表示確認をしています。Web アプリケーションの BASE とは独立したレポジトリで管理、運用されています。

一例を挙げると、商品が未登録のときに表示される「EmptyBox」というコンポーネントは、この BBQ の中に作られており、import で呼び出すだけで利用できるので各所で再利用が可能です。

f:id:Panda_Program:20211213135246p:plain
商品が購入されていないことを表すUI

Storybook ではこのように記述されています。

const Template = (args, { argTypes }) => ({
    components: { EmptyBox },
    props: Object.keys(argTypes),
    render(h) {
        return (
          <div>
              <section>
                  <h1>・ボタンあり</h1>
                  {/* empty box */}
                  <bbq-empty-box size={this.size} icon={this.icon}>
                        <div slot="text">
                              <p><span>まだ商品が登録されていません</span></p>
                              <p><span>商品を登録してあなたのネットショップを作りましょう</span></p>
                        </div>
                      <bbq-button type="submit" slot="action" width={this.buttonSize} icon="plus">商品を登録する</bbq-button>
                  </bbq-empty-box>
              </section>
              <section style={ { marginTop: '30px' } }>
                <h1>・ボタンなし</h1>
                {/* empty box */}
                <bbq-empty-box size={this.size} icon={this.icon}>
                      <div slot="text">
                          <p><span>商品が見つかりません</span></p>
                      </div>
                  </bbq-empty-box>
              </section>
          </div>
        )
    },
});

このように、BASE のデザインを体現した再利用可能なコンポーネントライブラリが BBQ です。 他にも Button や Modal、Calendar などの多数のコンポーネントがあります。コンポーネントの粒度は Atomic Design の Atoms から Molecules 相当に絞られています。

BBQ の直近の課題

さて、BBQ の直近の課題は大きく以下の2点です。

  • 既存のコンポーネントを改修した際に発生する DOM、CSS に起因する表示崩れを自動で検知できないこと
  • 依存モジュールのバージョンアップに時間がかかること

BBQ にはテストがありません。 ビジュアルリグレッションテストやコンポーネントテストがあれば、上記の2点は解決できるはずです。以前はスナップショットテストがあったそうですが、私が入社した時には既に CI 上でもローカルでも動かされていませんでした。

テストがないと、開発者は動いているコードの改修に対して萎縮してしまいます。 自分がバグを仕込まないか戦々恐々としながらコードを書き換えて何度も動作確認をし、最後は勇気を振り絞ってリリースをします。

もしそれでバグが出てしまったらロールバックをしなければなりません。その時間と手間に加えて、原因分析、事象の共有、再発防止策を考えるなど多くのことに時間が割かれてしまいます。テストがある場合は、ローカルや CI でテストが落ちていればそれを修正するだけでいいのにもかかわらず。

また、共通コンポーネント集はどの企業にとっても素早い開発速度を支える重要なツールであり、デザインや開発のコストを大幅に減らし、ユーザーにリリースを届けるスピードを加速させる大事な資産です。

その資産を守り、健全に育てるためにはやはりテストの導入が必要だと考えました。 そこで、まずは何らかのテストを導入することを BBQ のメンテチームのメンバーに相談しました。

BBQ に対するテスト方法、ツールの選定

BBQ にはメンテナンスチームがあります。メンバーは自分を含めた5人のフロントエンドエンジニアで、プロジェクトの合間にボランティアでメンテナンスをしています。 ライブラリのバージョンアップや、コンポーネント全体の見直しで発見した issue の対応などを行なっています(12月からはアドベントカレンダー6日目のアクセシビリティの記事を作成した@rry が加わって6人になりました)。

上記の BBQ の課題をメンテチームに共有し、E2E や VRT(ビジュアルリグレッションテスト)、コンポーネントテストやユニットテストなど色々なテスト方法がある中で、 BBQ に最適なテストは何か議論をしました。

みんなで議論した結果、VRT が BBQ の課題を解決する手段として最も良いと結論づけました。 議論の中で挙げられた検討事項は以下のようなものです。

  • E2E テストはそもそも今回の課題に見合っていないとして除外。 BBQ はコンポーネントのライブラリ集なのでページをまたぐ画面遷移やバックエンドまたはモックも必要になるようなログインといった粒度の大きなテストは不要だから
  • ユニットテストも除外。BBQ は Vue.js を クラスコンポーネントで記述しており、state を利用したロジックがほとんど。 このため、ロジックをユニットテストに切り出しにくいから
  • コンポーネントテストは、少し議論したけどやはり却下。 BBQ のライフサイクルを考えると、たくさんコンポーネントを追加・改修していくフェーズは終了しており、開発自体は活発ではない。 全てのコンポーネントに対してテストを書くコストは大きい一方で、既存のコンポーネントは数年間運用してバグが出ていないことが確認されており、既存のコンポーネントに対する新しい機能の追加も今はほとんどないため
  • VRT(ビジュアルリグレッションテスト) はちょうど良い。 現在は CSS の修正や、ロジックにあまり関係しない DOM のちょっとした修正がメインであり、開発者は表示崩れの有無を主に確認したいため。 スクリーンショットを撮って差分を表示するだけで実現できるため、コンポーネントテストより記述は少なくなる

以上が VRT を採用した理由です。 E2E やユニットテストを今は採用しなかった理由も記述しました。議論の中では最初に候補から外されたくらいメンバー間で不採用の理由は自明でしたが、本記事は意思決定の過程を紹介する記事なのであえて書き残しておきました。

コンポーネントテストは私から提案しました。BBQ は Input や Modal といったエラー状態や開閉状態を持つコンポーネントが存在するので、それらのテストもでき、ロジックが壊れていないことを担保できると考えたからです。

しかし、メンバーと議論して BBQ に対する理解を深めていく過程で、開発のホットな箇所をこそ守るべきだと考えを改め、VRT が適切だと判断しました。 幸い私は Storybook の VRT ツールである Chromatic の活用経験があったため、BBQ の課題は VRT で解決できるであろうことが予想できました。

課題の解決手段から考えるのではなく、テスト対象のアプリケーションの性質とその課題を考えることが重要だねとチームで確認しあいました。 そして VRT を導入しようとチームで決めた後は、 VRT を実施するツールの検討に入りました。

なお、これは将来に渡ってコンポーネントテストやユニットテストなどを導入しないということではありません。 実際、VRT の導入と並行して、BBQ にバグが出たときの再発防止策としてコンポーネントテストが導入され、過去にバグがあった一部のコンポーネントでデグレが起こらないようにテストが記述されました。

今回は BBQ 全体に関わるテストの導入という文脈であることをご理解頂ければと思います。

VRT を実施するツール。reg-suit と Chromatic

VRT とは何か

そもそも VRT(ビジュアルリグレッションテスト)とは何でしょうか。 システム開発の文脈でリグレッションという単語は「新規開発に伴って、既存の機能が正常に動作しなくなること」を指します。これはデグレとも呼ばれます。

リグレッションテストは、「コードを書き換えても以前の機能が正常に動作することを担保するテスト」です。それを視覚の面からテストしたものがビジュアルリグレッションテストです。 具体的には、画面(ブラウザ)にページやコンポーネントを表示してスクリーンショットを撮影し、現行の UI と改修後の UI を比較して差分の有無を検出することで実現されます。

reg-suit と Chromatic

この VRT を実施するツールとして有名なのは reg-suit です(「レグスイート」と呼んでる人もいますが、suit の発音は「スーツ」です)。 reg-suit について調べた結果、今回は以下の理由から採用を見送りました。

  • reg-suit は画像の差分を解析して diff を表示してくれるツールであるため、画像は自分たちで別途準備する必要があるため
  • reg-suit には便利な plugin が揃っているが、plugin の選定をしたり、設定ファイルを書いたり、S3 のバケットを準備したりする必要があり、Chromatic と比較すると準備量が多いため

先程記述したように自分は Chromatic の利用経験があったため、コマンドを一行実行するだけで Storybook をビルドして publish できる体験が忘れられませんでした。 しかも全てのストーリーに対してコミットごとの差分を解析、表示してくれる Chromatic は、導入するのも捨てるのも楽だと考えていました。

特に、Chromatic は Storybook のメンテナーが作成したツールであり、BBQ は Storybook で表示確認をしている点も考慮すると、今回は Storybook のエコシステムに乗った方が良いという判断をしました。 reg-suit が悪いわけではなく、こちらも上記のテスト方法の検討結果と同様に BBQ というアプリケーションに対しては Chromatic の方が適切だと判断しただけです。

なお、Chromatic の具体的な使い方については別の記事をご参照ください。

以上のような意思決定の過程と結論をマネージャーに共有したところ、まずは無料プランで導入し、 BBQ のメンテナンスチームの開発メンバーが Chromatic を使った開発フローを体験した上で、良し悪しを判断しようということになりました。

そこで、実際に Chromatic を一部のブランチに適用していく準備を始めました。

Chromatic を効果的に活用するための準備

ストーリーの数を増やす

Chromatic を BBQ に導入する前に、コンポーネントのストーリーのバリエーションを増やしました。 デザイン崩れを検知するためには、コンポーネントの様々なバリエーションを予め表示しておき、カバー範囲を広げないと VRT の効果が半減するからです。

コンポーネントを改修した際に、特定の Props を与えたときにだけ表示される状態の考慮が漏れていて、気づかないところでバグが発生している可能性もあります。 そのようなバグを未然に防ぐために、様々な Props を与えたコンポーネントのストーリーを作成して管理下に置くことが理想です。

具体的には、以下に挙げる Search コンポーネントのように、Storybook の CSF(Component Story Format)の機能を利用して、 Props を部分的に変えて一つのコンポーネントのバリエーションを網羅するようにしました。

import { action } from "@storybook/addon-actions";
import { Story } from "@storybook/vue";
import Vue, { VNode } from "vue";

import README from "./README.md";
import Search from "./Search.vue";

export default {
  title: "Elements/Search/Search(Vue)",
  parameters: {
    notes: { README },
  },
};

type Props = {
  name: string
  placeholder: string
  keyword: string
  disabled: boolean
}

const Template: Story<Props> = (args, { argTypes }) => Vue.extend({
  components: { Search },
  props: Object.keys(argTypes),
  render(h) {
    const { name, placeholder, keyword, disabled } = this.$props

    return (
      <div style={{ padding: "40px" }}>
        <bbq-search
          name={name}
          placeholder={placeholder}
          disabled={disabled}
          vModel={keyword}
          onSearch={(val: any) => this.search(val)}
          onChange={this.change}
          onInput={this.input}
        />
      </div>
    ) as VNode
  },
  methods: {
    input(val: string) {
      action('input')(val)
    },
    search(val: string) {
      action('search')(val)
    },
    change(val: string) {
      action('change')(val)
      this.$props.keyword = val
    },
  },
});

export const Default = Template.bind({})
Default.args = {
  name: 'search',
  placeholder: '商品名・説明から検索',
  keyword: '',
  disabled: false,
};

export const NoPlaceholder = Template.bind({})
NoPlaceholder.args = {
  ...Default.args,
  placeholder: '',
};

export const WithKeyword = Template.bind({})
WithKeyword.args = {
  ...Default.args,
  keyword: 'おいしい肉',
};

export const Disabled = Template.bind({})
Disabled.args = {
  ...Default.args,
  disabled: true,
};

f:id:Panda_Program:20211215111648p:plain
さまざまな Props を渡す

もちろん Search 以外にも Button や Input のような全てのコンポーネントに対してこのような変更を加えています。 Props のバリエーションを増やすことで、結果的にストーリーの数が当初の79から148に増えました。

GitHub Actions でコミットを push するたびにビルドする

Chromatic を導入しても常に活用しなければ意味がありません。 そこで、ブランチごとに、またコミットの push のたびに Chromatic をビルドするために GitHub Actions(GHA) を活用しました。 Chromatic 用の GHA のテンプレートは公式で用意されています。

このため、開発者としてやるべきことはこのテンプレートを使うことと、ビルド用のトークンを GitHub レポジトリのシークレットに埋め込むことだけした。これで各ブランチで Chromatic をビルドする準備ができました。

Chromatic の効用をチームで体感する

dependabot が作成するブランチに適用する

便利なツールを導入した後はチームで運用し、ツールに対して知見をチームで溜めていくことが次のステップです。 BBQ のチームメンバーが Chromatic を体験してみるにあたり適切な粒度のタスクを探したところ、モジュールのバージョンアップデートがちょうどいいように思われました。

従来、dependabot によってバージョンアップデートのブランチが作成されたときは、担当しますと手を上げた開発者のローカル環境で手による動作確認がされていました。 具体的には、 Storybook をローカルで立ち上げて全部のコンポーネントを表示、チェックして、以前のバージョンと比較して問題ないことを確認していました。

しかし、この手法は確認に時間がかかる上に、「ビルドは通ってるし目視確認はしたけど本当にバグは出ていないと自信を持ってまでは言えない」という雰囲気がメンテチーム内にありました。 このため、心理的な負担もありモジュールのバージョンアップデートの対応は即座になされるわけではありませんでした。

ただし、脆弱性のあるパッケージのバージョンが上がっていないなどクリティカルな問題があるというわけではなく、dependabot があるパッケージのバージョンアップのブランチを作ってみんな気づいているけど、2〜3週間放置されている間に次のバージョンがリリースされたというような状況です。

パッケージのバージョンアップデートの理想状態は、CI でテストとビルドをしてエラーが出たらアップデートをやめ、CI が Fail しなければ master マージするという運用です。 テストがある場合は人がアドホックに動作確認をする必要がなくなります。しかし、BBQ にはテストがないためそれができていなかったのです。

差分がないから安心してリリースできる

この課題は Chromatic で一定程度解決できると予想していました。 BBQ は UI コンポーネント集のため、ロジックに関するライブラリはほとんど入っていません(date-fns くらい)。依存ライブラリは Webpack の loader や eslint、stylelint といったビルド時に必要なツールくらいです(このため、パッケージのアップデートが遅れてもクリティカルな問題にはならなかったのです)。

dependabot が作成したブランチの CI を実行し、Chromatic で Storybook をビルド、表示するようにしました。 Chromatic 上ではブランチの baseline と呼ばれる時点のスクリーンショットとの差分が解析されます詳しくはブランチとベースラインの解説は公式ドキュメントをご覧ください。)。

テストと同様に、差分があれば Chromatic 上で目視チェックをし、差分がなければ問題ないとして master マージをすれば良いのです。 Chromatic が Storybook をビルドして URL を発行してくれるため、わざわざローカルで Storybook を立ち上げる手間が省けます。

また、差分があるコンポーネントは Chromatic がピックアップしており、差分のがある箇所もハイライトをつけてくれているため、抜け漏れがありません。

これは動作確認の時間の大きな節約になる上に、開発者としても不安なくパッケージアップデートを取り込んだ新バージョンの BBQ リリースできます。 実際にいくつかのパッケージのバージョンを上げましたが、以前と違ってとても楽になりました。

人間の目では気づかない差分もカバーしてくれる

Chromatic の試用期間中に、アイコンに変更があるという差分が検知されました。

しかし、元のアイコンと新しいアイコンを見比べてみても全く同じように見えます。エンジニアメンバーが手を加えた覚えはなく、Chromatic のご検知かと疑いながら念のためデザイナーのチームに確認してみました。

すると、デザイナーから「特定の環境下で表示がおかしかったので、少し前に SVG の不要なパスを削除した。見た目には変更がない」とコメントがあり、それはまさにこの差分のことでした。言われてみると、確かに以前のアイコンの線が少し太いように見えます。

f:id:Panda_Program:20211213135257p:plain
Chromatic が検知したアイコンの差分

今回は拡大しているのでなんとなく差が分かりますが、Storybook 上のアイコンは 14px という小さいサイズで表示されているため、目で見ても差分が分かりませんでした。 Chromatic はこんなところまで検知してくれるのかという驚きで、BBQ メンテチームが盛り上がると共に Chromatic に対する信頼感が増しました。

VRT がある開発フロー

開発フローに Chromatic を組み込む

Chromatic は開発フローの中で、コードレビューのフェーズで活用しています。 コードと表示に変更があった場合、その表示の変更は意図したものであるかを確認するためのレビューです。コードレビューの中に、デザイン面の差分のレビューを組み込むイメージです。

Chromatic は GitHub と連携可能であるため、PR の画面からビルドの状態を知ることが可能です。下記の画像では「UI Review」と「UI Tests」に緑のチェックマークがついていますが、もし表示に差分があるときは黄色の丸いマークが表示されます。

f:id:Panda_Program:20211213135307p:plain
GitHub の PR の Chromatic の状態のフィードバック

Chromatic のサイト上で差分のあるコンポーネントの画像がピックアップされているので、レビュワーはそのコンポーネントに対するレビューをします。 気になる点があれば Chromatic 上でコメントを記入し、問題なければ Chromatic 上で Accept をします。

上記の画像に緑のチェックマークがついているのは、そもそも差分がない場合か、全ての差分に対してレビュワーが Accept をした場合のどちらかです。この画像では差分があったけど Accept しているため、「UI Tests」の説明に「4つの変更が基準として Accept された」と書かれています。

「レビュワーの責任範囲はコードレビューまでで、動作確認や表示確認はブランチの開発者の責務」と考える方もいますが、BBQ では Chromatic の導入に伴い表示確認もレビュワーにお願いすることになります。

ただし、Chromatic が差分を予め検出しているため、レビュワーの負担が大きく増えるようなことはありません。 むしろ、レビュワーが表示の変更点を網羅的に知れるという知識の標準化に役立つ上に、見た目の変更を伝えるために前後比較のスクリーンショットを撮って PR に貼り付けるというレビュイーの手間も省けます。

これが VRT を組み込んだ開発フローです。

Chromatic に対する BBQ メンテチームのメンバーの意見

BBQ メンテチームのみんなに Chromatic を一通り触ってもらった後、マネージャーに Chromatic を触った感想を報告する会が開かれました。そこでは以下の声を聞くことができました。

  • 手元で Storybook を立ち上げていたが、その必要がなくなった
  • 変更点をまとめて Chromatic で見れる
  • 変更があったコンポーネント数が表示される
  • 差分が全て記録されているので安心感がある
  • 人の目でわからない差分を検知してくれた
    • アイコンが変更されていた

もちろん懸念点も少しありました。例えば「Chromatic 上のコメントが PR や Slack に通知できず、メールでの通知だけである上に、URL がコメントへのリンクになっていない」というものです。ただ、懸念点がどれもツール側の話なのでこちら側でできることはなく、とりあえず改善要望を出しておきました。

以前から折を見て議論を重ねており、マネージャーにも逐次共有して意見を聞いてきたため、BBQ に VRT を導入することに対する反対や Chromatic ではない別のツールが良いなどの声は出なかったためホッとしました。

以上が、BASE の BBQ という共通 UI コンポーネントライブラリのレポジトリに Chromatic を導入するという意思決定の過程でした。

終わりに

フレームワークやライブラリ導入の技術選定に関する記事は枚挙にいとまがないですが、外部ツールを選定するためのプロセスや意思決定に関する記事はあまり見ないなと思い、本記事を書いてみました。

Chromatic の導入を例に挙げていますが、以下の点を意識して読んで貰うとより一般的な視点が得られると思います。

  • 適切なツールを選ぶためには、まず現在開発しているアプリケーションの性質をつぶさに観察すること
  • 社内で使用するツールはマネージャーと二人三脚で選定すること。ツールの調査や社内に使い方を広めるのは現場のエンジニアの役割であるが、導入可否の最終決定者はマネージャーであるため
  • ツールを使い続けるためにチームからフィードバックを受けること。一人が特定のツールの導入を陣頭指揮してもいいが、ツールを使うのはその人だけではないため

なお、Chromatic の具体的な使い方については別の記事で紹介します。Chromatic の導入や使ってみたいツールの導入に役立てば幸いです。

明日の記事は坂東さんの「10人以上のPJでリモートランチ会をするときの工夫したこと」です。