カミナシ エンジニアブログ

株式会社カミナシのエンジニアが色々書くブログです

StoryShotsの可能性を探る

こんにちは、カミナシの@tomiです。

前回は、Expo ReactNativeにStorybookの導入を行いました。

今回は、StoryShotsを入れてみたいと思います。

StoryShotsとは、自動スナップショットテストができるStorybookのアドオンです。 内部的には、JestのSnapshotが動作し、UI が予期せず変更されていないかを確かめるツールです。

なぜ入れてみようと思ったかというと、ただStorybookを導入しただけでは、メンテナンスされずに腐ってしまうのが目に見えているので、楽に忘れることなくstoryの更新ができる方法ないかなと調べていると、よくStoryShotsを目にしたので試してみることにしました。

StoryShotsの実際の動作を見て、今後の運用に必要かどういったときに役に立ちそうかを体験したいと思います。

StoryShotsを導入する

Storybookの導入は前回の記事で済んでいるので、StoryShotsのアドオンを入れます。

yarn add -D @storybook/addon-storyshots@^5.3
# monorepoの場合↓
# lerna add @storybook/addon-storyshots@^5.3 --scope=mobile --dev

ReactNativeに対応しているStorybookのバージョンに合わせて5.3をインストールしました。

/storybookディレクトリにStoryshots.test.tsファイルを作成。

// Storyshots.test.ts
import path from 'path';
import initStoryshots from '@storybook/addon-storyshots';

initStoryshots({ configPath: path.resolve(__dirname, '../storybook') });

ドキュメントでは、initStoryshots()だけで動くようでしたが、monorepo構成だからかconfigPathでStorybookの設定を行っている場所を指定してあげる必要がありました。

次に、StoryShotsの実行用のjest.configを設定します。

jest.config.jsと同じ場所にjest.config.storyshots.jsを作成。

// jest.config.storyshots.js
const path = require('path')
const baseConfig = require('./jest.config')

module.exports = {
  ...baseConfig,
  testMatch: ['**/packages/mobile/storybook/Storyshots.test.ts']
}

testMatchの部分を、StoryShots用のテストだけを読み込むように設定します。

こうすることで、ユニットテストと自動スナップショットテストを別で実行できるようにしています。

StoryShotsを実行するscriptを設定します。

// package.json
"scripts": {
  "storyshots": "jest -c jest.config.storyshots.js"
}

StoryShotsを実行する

では、実際にStoryShotsを実行するとどういう結果がもらえるのかを見てみます。

yarn storyshots

f:id:kaminashi-developer:20210422135244p:plain

*.stories.jsが存在する3件のSnapshotsが生成に成功しました。

そして、__snapshots__/Storyshots.test.ts.snapというファイルが作成されました。

中身は、Componentファイルがレンダリングされた形のようです

f:id:kaminashi-developer:20210422124254p:plain

差分がでるとどうなるのか

スナップショットが作成されたので、次にコンポーネントを更新して差分を出すとどういう結果がでるかを確認しようと思います。

Welcome/index.jsというコンポーネントに適当にテキストを追加して、再度SnapShotsを実行してみます。

f:id:kaminashi-developer:20210422124334p:plain

すると1件のスナップショットが失敗しました。

失敗した内容として今回変更した部分が表示されます。

f:id:kaminashi-developer:20210422124401p:plain

これが意図した変更の場合はスナップショットを更新しましょう。実行コマンドに-uオプションを付けてあげます。

yarn storyshots -u

1件が更新されました。

f:id:kaminashi-developer:20210422124435p:plain

なるほど、なるほど。 StoryShotsがどういうものか掴めてきました。(いやな予感がする・・・)

次に、Welcome/index.jsを使っているコンポーネントWelcome/Wrap.tsxを作成してみます。

// Welcome/Wrap.tsx
import React from 'react'
import Welcome from './index'

const Wrap: React.FC = () => {
  return <Welcome />
}

export default Wrap

Storyにもこのコンポーネントを追加します。

// Welcome/Welcome.stories.jsに追記
import Wrap from './Wrap'

storiesOf('Welcome', module).add('to Storybook Wrap', () => <Wrap showApp={linkTo('Button')} />);

そして、StoryShotsを実行します。

yarn storyshots

1件追加されました。

f:id:kaminashi-developer:20210422124518p:plain

では、ここでWelcome/index.jsを更新したらどうなるでしょうか?

以下が実行結果です。

f:id:kaminashi-developer:20210422124545p:plain

2件のスナップショットにエラーがでました。

失敗したスナップショットはWelcome/index.js自身とそれを使用しているWelcome/Wrap.tsxです。

StoryShotsによって、コードを変更した際の影響範囲がわかることで、予期せぬところで使われていて画面が崩れてしまったといったミスを防ぐことができそうです。

StoryShotsの効果

StoryShotsを実際に触ってみて、受けれる恩恵は大きく2点かなと感じました。

Storybookの更新漏れを防げる

ただのUI変更ではほぼStoryShotsは必要ないでしょう。

ですが、例えばButtonコンポーネントにcolorというプロパティを追加したとき、StoryShotsを入れておくことで、storyの更新もしなければと気づくことができます。

ただし、プロパティを追加してyarn storyshots -uで更新してみましたが、テストが通ってしまったので完全にStorybookの更新漏れを防ぐことができないかもしれません。

f:id:kaminashi-developer:20210422124602p:plain
Warningは出ているが、StoryShotsは更新される

予期せぬ更新を防げる

変更したコンポーネントが想定しているところ以外に使われていることに気づけるので、レイアウトの崩れや動作しないといった不具合を減らすことができそうです。

ただし、Storybookが入っているところだけが検知できるので、すべてのコンポーネントにStorybookが入っていないとあまり意味がなさそうです。

まとめ

StoryShotsを導入して、どういう効果があるかを確認してみました。

結果的にいまのフェーズでStoryShotsを入れるのは、あまり効果が期待できないと感じました。

Storybook導入の目的が新デザインガイドラインの運用・管理なので、基本となるコンポーネントにしかstoryを追加しないため、恩恵を受けづらそうです。

プロジェクトのはじめからStorybookを入れており、全コンポーネントのstoryを作成していたのであれば、非常に重要なツールかなと思います。