Yutteee
2025-06-15

UIコンポーネント開発の個人的テスト戦略

Storybook v9のリリースもあり、Storybookはテストツールとしても大きく進化しています。ビジュアルリグレッションやインタラクションテスト、アクセシビリティチェックなど、UIコンポーネント開発における多角的な品質保証がStorybook上で完結できるようになりつつあります。

一方で、VitestのようなユニットテストフレームワークとStorybookの役割や境界はどのように分けるべきか悩みました。

そこで本記事では、TDD(テスト駆動開発)を軸に、UIコンポーネント開発におけるテスト戦略について自分なりに整理してみました。StorybookとVitestをどう使い分けるか、どんなディレクトリ構成や運用が実用的かについてまとめています。

目指すUIコンポーネント開発体験

  • UIコンポーネント単体での開発・検証が容易であること
  • TDDによって安心して着実に実装できること
  • アクセシビリティを高いレベルで担保できること
  • 多角的なテストが可能であること

TDD(テスト駆動開発)とは

TDD(Test Driven Development、テスト駆動開発)とは、「テストを書く→実装する→リファクタする」というサイクルを繰り返しながら開発を進める手法です。まず最初に、実装したい機能や振る舞いをテストコードとして記述し、そのテストが失敗することを確認します。その後、テストが通る最小限の実装を行い、最後にリファクタや改善を加えます。

この手法を用いることで、以下のようなメリットがあります。

  • 仕様や要件が明確になりやすい
  • バグの早期発見・防止につながる
  • 安心してリファクタや機能追加ができる

また、AIコーディング時代でTDDはより有効になります。テストという明確で客観的なゴール設定により、AIの探索範囲を制御しつつ、AIの生成したコードをフラットに評価できます。

StorybookとVitestの役割分担

Storybookの役割

  • ビジュアルリグレッションテスト:見た目の変化を検知し、予期せぬ変更を防ぐ(Chromatic連携)
  • インタラクションテスト:コンポーネントの挙動を視覚的に確認・テスト
  • アクセシビリティテスト:axe-coreによる自動チェック

インタラクションテストは、コンポーネントの挙動をテストします。これはVitestでもテストできますが、Storybookで行うことで、インタラクションを視覚的に確認できます。

また、storybook-addon-test-codegenを活用することで、Storybook上でコンポーネントとのインタラクションを記録し、アサーションを追加し、テストを保存することで、簡単にテストを追加できます。

Vitestの役割

  • ユニットテスト:コンポーネントの期待する振る舞いを実装

Storybookのテストだけでは不十分な部分を、Vitestで補完します。

以下のコードのように、期待する要素が正しいHTMLタグでレンダリングされるか、正しい属性が当たっているかなどをテストします。

describe("Header", () => {
  it("header要素でレンダリングされる", () => {
    render(<Header />);
    expect(screen.getByRole("banner")).toBeInTheDocument();
  });

  it("トップという名前のリンクが存在し、homeに戻る", () => {
    render(<Header />);
    const link = screen.getByRole("link", { name: "トップ" });
    expect(link).toBeInTheDocument();
    expect(link).toHaveAttribute("href", "/");
  });

  it("PC用ナビゲーションに私について・プロダクト・記事のリンクが存在する", () => {
    render(<Header />);
    const nav = screen.getByRole("navigation", { name: "メインナビゲーション" });
    expect(nav).toBeInTheDocument();
    expect(within(nav).getByRole("link", { name: "私について" })).toBeInTheDocument();
    expect(within(nav).getByRole("link", { name: "プロダクト" })).toBeInTheDocument();
    expect(within(nav).getByRole("link", { name: "記事" })).toBeInTheDocument();
  });
  // ...
});

UIコンポーネントが開発者の期待する振る舞いをするかどうか、TDDを用いて着実に実装します。

ディレクトリ構成とファイルの役割

具体的な構成をご紹介します。UIコンポーネント開発を効率化・高品質化するため、2つのディレクトリで責務を明確に分離しています。

  • /ui:見た目を担う純粋なUIコンポーネント群
  • /features:機能を担う複合コンポーネント群

ui - 見た目だけの純粋なUIコンポーネント

  • index.tsx: 見た目・アクセシビリティ・props設計に特化した再利用可能なUI
  • index.module.css: スタイル定義
  • index.stories.tsx: Storybook用。各propsパターンの確認を行う。
  • index.test.tsx: ユニットテスト(TDD用)

features - 機能単位の複合コンポーネント

  • index.tsx: 機能を持つコンポーネントのエントリ
  • presenter.tsx: UI部品を組み合わせて機能を実現。状態管理やロジックはpropsで受け取る。
  • index.module.css: スタイル定義
  • index.stories.tsx: StorybookでインタラクションごとにStoryを作成
  • index.test.tsx: ユニットテスト(TDD用)

インタラクションテストに関しては、Storybook上でTDDを行います。各Storyは確認したいインタラクションごとに作成します。これによって、それぞれのfeatureコンポーネントの期待する動作をStorybook上で一目で確認することができます。

storybookのスクリーンショット。Headerコンポーネントの各Storyに期待するインタラクションを指定している。

CI/CDと自動テスト

CI/CDにはGitHub Actionsを活用しています。プルリクエストごとに、Storybookのビジュアルテスト・インタラクションテスト・アクセシビリティテスト、Vitestのユニットテストが自動的に実行されるように設定しています。

これにより、予期せぬ見た目の変更やアクセシビリティの低下を未然に防ぐことができ、常に高品質なUIコンポーネントを保つことが可能になります。

まとめ

本記事では、TDDを軸に、StorybookやVitest、CI/CDを活用したUIコンポーネント開発の戦略について紹介しました。

TDDを実践することで、仕様や要件を明確にしながら、安心してリファクタや機能追加ができる堅牢なUIコンポーネントを開発できます。Storybookによるビジュアル・インタラクション・アクセシビリティテスト、Vitestによるユニットテストを組み合わせることで、多角的な品質保証が可能になります。