React + storybook + Backstop.js でビジュアルリグレッションテストする

## storybook
> storybooks/storybook
> https://github.com/storybooks/storybook

storybookは各コンポーネントごとに独立したスタイルガイドを生成する機能です。
Reactであれば作成したコンポーネントを快適に閲覧することができます。
![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/80473/7361b14a-fe10-773d-66c5-2d94bd910ada.gif "demo.gif")

## ビジュアルリグレッションテストとは
例えばあるコンポーネントを更新した場合、どこかで表示崩れが発生しているかはそのページを見ないと気づけませんでした。
上述したstorybookを利用しAtomic Designなどを取り入れていれば、Templates層を見ることで、ある程度のUIは把握できるようになるでしょう。
ただしページ数が増えてくるとコンポーネントの更新毎にすべてのTemplates層を見ることは煩わしく感じられます。

ビジュアルリグレッションテストは視覚的な回帰テストを指します。
スクリーンショットを撮影し前後の差分を利用するテストです。これによって毎回人が見ることなく、UIの変化をテストすることが可能になります。

## Backstop.js
> garris/BackstopJS
> https://github.com/garris/BackstopJS

ビジュアルリグレッションテストを行うためのツールです。
backstop.jsonという設定ファイルをルートに生成し、オプションなどを追加します。この状態でコマンドを実行するとヘッドレスブラウザが起動しスクリーンショットを撮影します。
テスト時は撮影したスクリーンショットの前後比較を行います。
テスト結果はhtmlとして別途出力され、GUI上で差分は把握することができます。

## 環境構築
前置きが長くなりましたが、実際に触っていきましょう。
筆者の環境がmacのためwindowsで正しく動作しない可能性があることをご了承お願いします。
また、前提としてNodeはインストールされているものとして勧めます。verは指定しませんがLTSされているverを前提にします。
筆者がyarnを使っている都合でコマンドはyarnで書いています。

### ディレクトリ構造
```
.storybook
	- config.js
src
	- Components
		- Caption
			- index.js
			- index.stories.js
.babelrc
```

### パッケージのインストール
styled-componentsは別のツールに変えても問題ありません。
Backstop.jsだけはグローバルインストールが必要です。

```
$ yarn add react react-dom styled-components -S
$ yarn add @storybook/react -D
$ yarn add babel-core babel-preset-env babel-preset-react babel-preset-stage-0 -D
$ npm i backstopjs -g
```

### 設定ファイル

#### babel
```.babelrc
{
    "presets": [
        "env",
        "react",
        "stage-0"
    ]
}
```

#### storybook
storybookの設定ファイルは`.storybook/config.js`で行います。
以下のように記述することで`./src/Components`内に存在する`.stories.js`で終わるファイルをすべて走査します。
これでコンポーネントが増えても自動的にstorybookに追加されるようになります。

```js:.storybook/config.js
import React from 'react';

const req = require.context("../src/Components", true, /.stories.js$/);

function loadStories() {
    req.keys().forEach((filename) => req(filename));
}

configure(loadStories, module);
```

#### package.json
`scripts`はこんな感じにします。それぞれの項目は後述します。

```json:package.json
...
"scripts": {
    "storybook": "start-storybook -p 6006",
    "spec:visual": "backstop reference",
    "test:visual": "backstop test",
    "approve:visual": "backstop approve",
}
...
```

## コンポーネントの作成
コンポーネントは以下のように記述します。

```jsx:./src/Components/Caption/index.js
import React from 'react';
import styled from 'styled-components';

const CaptionEl = styled.h1`
    font-size: 1.6rem;
    color: #333;
`;

const Caption = ({...props}) => <CaptionEl {...props} />;
export default Caption;
```

続いてstorybookを作るためのファイルを更新します。

```jsx:./src/Components/Caption/index.stories.js
import React from 'react';
import {storiesOf} from '@storybook/react';

import Caption from './index';

storiesOf('Caption', module).add('default', () => <Caption>Caption is here.</Caption>);
```


## storybookを確認
storybookは以下のコマンドで実行できます。

```
$ yarn storybook
```

問題なく起動できれば、`http://localhost:6006`でサーバーが起動します。アクセスしコンポーネントが表示されていれば成功です。

![スクリーンショット 2018-05-30 16.46.45.png](https://qiita-image-store.s3.amazonaws.com/0/80473/9d06ce18-b1a8-fe14-8415-9685cca08f5b.png "スクリーンショット 2018-05-30 16.46.45.png")

実際にcssなどを触ると更新されることもわかるかと思います。

## Backstop.jsの構築
Backstop.jsはグローバルにインストールが必要です。
上述したコマンドでインストール済みであれば以下のコマンドを叩いてイニシャライズしましょう。

```
$ backstop init
```

プロジェクトルートに`backstop.json`という設定ファイルが生成されます。これを以下のようにします。
`scenarios`のところで何をテストするのかを設定します。`URL`はstorybookのナビゲーション部分を省いた状態でコンポーネントのみを表示するURLを入力します。

```json:backstop.json
{
  "id": "backstop demo",
  "viewports": [
    {
      "label": "PC",
      "width": 1024,
      "height": 768
    }
  ],
  "onBeforeScript": "puppet/onBefore.js",
  "onReadyScript": "puppet/onReady.js",
  "scenarios": [
      {
          "label": "Caption",
          "url": "http://localhost:6006/iframe.html?selectedKind=Heading&selectedStory=default",
          "misMatchThreshold": 0.0000000001
      }
  ],
  "paths": {
    "bitmaps_reference": "backstop_data/bitmaps_reference",
    "bitmaps_test": "backstop_data/bitmaps_test",
    "engine_scripts": "backstop_data/engine_scripts",
    "html_report": "backstop_data/html_report",
    "ci_report": "backstop_data/ci_report"
  },
  "report": ["browser"],
  "engine": "puppeteer",
  "engineFlags": [],
  "asyncCaptureLimit": 5,
  "asyncCompareLimit": 50,
  "debug": false,
  "debugWindow": false
}
```

## ビジュアルリグレッションテスト
実際にテストしましょう。
backstop.jsは`backstop.json`の`scenarios`に記載されたコンポーネントのスクリーンショットを撮影します。
そのためまずはstorybookを立ち上げます。

### スクリーンショットの撮影

```
$ yarn storybook
```

その状態でターミナルのタブを1つ増やし、追加したタブで以下のコマンドを叩きます。

```
$ yarn spec:visual
```

これでスクリーンショットが撮影された状態になります。

### リグレッションテストの実行
続いてテストコマンドを叩きます。

```
$ yarn test:visual
```

スクリーンショットを再度撮影し、前後比較してテストを行います。
差分がないため今回はパスされます。
ここで`Caption`コンポーネントを更新してみます。

```jsx:./src/Components/Caption/index.js
import React from 'react';
import styled from 'styled-components';

const CaptionEl = styled.h1`
    font-size: 1.2rem;
    color: #666; 
`;

const Caption = ({...props}) => <CaptionEl {...props} />;
export default Caption;
```

再度テストしてみましょう。

```
$ yarn test:visual
```

今度はエラーで出てくるはずです。

![スクリーンショット 2018-05-30 16.50.42.png](https://qiita-image-store.s3.amazonaws.com/0/80473/0314ae52-746f-1349-e935-50779a2c47d5.png "スクリーンショット 2018-05-30 16.50.42.png")


このようにスクリーンショットに差異が出るとツール側でエラーを出してくれます。
例えばAtomic DesignのTemplates層などをこのツールで管理すると、意図しないカラム落ちなどは未然に防ぐことができるようになります。

### 変更の承認
変更を承認したり、`backstop.json`の`scenarios`などを追加する場合、スクリーンショットを撮影するために毎回storybookを起動するのは面倒です。
これを回避するために`backstop approve`が用意されています。本プロジェクトでは以下のコマンドです。

```
$ yarn approve:visual
```

上記のコマンドでスクリーンショットが更新されるため次回からはエラーが発生しないようになります。

## 終わりに
このように今まで人が見なければならなかった部分もツールを使うことで大部分を自動化することが可能になっています。
ぜひプロジェクトでも生かしていけるように自分もがんばります〜:baby: