🍣

フロントテストの導入

2024/04/17に公開

はじめに

すでにバックエンドのテストは導入されているため、今回はフロントテストの導入を行いました。

なぜフロントテストを導入しようと思ったのか?

開発生産性向上の取り組みを行うにあたり、テストを書いた方が機能追加や、リファクタリングなどがしやすくなり、長い目で見てテストがある方が開発生産性が向上すると判断したからです。
またコード変更毎に手動で確認する手間も省け、開発効率も向上するためです。

テストの利点

テストの利点についてはさまざまな書籍で話されているので詳しく書く必要はないと思いますが、主に以下の利点があります。

  • バグの早期発見
  • リファクタリングの安全性向上
  • 新規機能追加時の安心感
  • コードの品質保持
  • 開発プロセスの効率化

などが挙げられます。

なぜJestにしたのか?

初めは、Vitestの導入を検討しておりましたが、Node.jsやWebpackのバージョンアップが必要であり、工数とコストの観点から Jest を選択しました。
また Jest は、豊富なドキュメントやコミュニティが活発なところも魅力的でした。

Jest導入の詳細

今回インストールしたバージョンは Jest29.7.0 です。
導入については基本的に Jest公式ドキュメントを参考にして進めていき、細かい部分で サバイバルTypescirptを参考に進めました。

  1. Yarnを使ってJestをインストール

    yarn add --dev jest
    
  2. Babel設定:
    JestでES6構文を使用するには、Babelが必要です。
    babel.config.js ファイルをプロジェクトのルートに作成し、以下を追加します。

    module.exports = {
      presets: [
        '@babel/preset-env',
      ],
    };
    
  3. Jest設定ファイルを作成:
    Jestの設定ファイルjest.config.jsをプロジェクトのルートに作成し、必要に応じて設定を追加しました。今回はTypeScirptで書かれたコードもテストしたかったためその設定も追加しました。

    module.exports = {
      preset: 'ts-jest',
      testEnvironment: 'node',
    };
    
  4. テストスクリプトをpackage.jsonに追加:
    package.jsonにテストコマンドを追加します。

    "scripts": {
      "test:jest": "jest", // 実行ファイルの範囲指定も可能です。
    }
    
  5. テストファイルの作成:
    __tests__ディレクトリを作成し、テストファイルを配置します。ファイル名は.test.js.spec.jsとすることが一般的ですが、 .test.js のが多いようなのでそちらで運用することに決めました。

  6. Jestを実行:
    以下のコマンドでテストを実行します。

    yarn run test
    

eslint-plugin-jest

テストコードを書く際に、 test関数 や it関数 のどちらでも書けるため、統一して書けるようにするためにJest用のeslintライブラリも導入しました。
インストールの方法や設定方法は公式ドキュメントを参考に行いました。

{
  "extends": ["eslint:recommended", "standard", "plugin:jest/recommended"],
  .
  .
  "rules": {
    .
    .
    "jest/consistent-test-it": ["error", {"fn": "it"}], // testではなくitを使う
    "jest/require-top-level-describe": "error", // describeが必要
    "jest/no-identical-title": "error", // describeで重複タイトルを許可しない
    "jest/valid-expect": "error" // expectの記述が正しいか
  }
}

今回は、すでに導入されているバックエンドテストフレームワークの Rspec と書き方を合わせるために、 test関数 ではなく it関数 を使うようにルールを設定追加しました。
その他にもいくつかのルールを .eslintrc.js に追加しました。

単体テストの限界

単体テストは導入コストが低いため比較的簡単に導入できます。
しかし単体テストのみだと、関数やメソッドを個別にテストした時は成功していたとしても、通しで動かしてみると途中で失敗しているケースがあり、そのことに気づくことができない場合があります。
具体的には以下の違いがあります。

  • 単体テスト(個別テスト)
    • 役割
      • 1つの関数。1つのコンポーネント。それぞれ隔離された各々をテストする。
    • メリット
      • 関数やメソッドそれぞれ1つのみテストするためテストしやすい。書きやすい
    • デメリット
      • 単体の動きは保証できても、それぞれを組み合わせた時の全体のテストができないため、全体で見ての安全を保証できない。
  • 結合テスト(全体をテスト)
    • 役割
      • 複数の関数やコンポーネントを同時にテストする。
      • 組み合わさった違う関数同士の結果をテストしたり、関数とコンポーネントを組みわせてテストしたりする。
      • よりリアルに近い全体のテストができる。
    • メリット
      • それぞれの関数やコンポーネント全体をテストするため、少ないテストで幅広い関数やコンポーネントをカバーすることができる。
    • デメリット
      • 単体テストより書くコストが少し高い。

以上のことから、今回のテストでは、Reactコンポーネントのテストも含めて「結合テスト」を行うために React Testing Library も導入しました。

React Testing library

他にもいくつかコンポーネントテスト用のライブラリはありますが、今回のフロントはReactメインだったため、Reactに特化して作られたライブラリである React Testing Library を選択しました。
導入方法に関しては、公式ドキュメントgithubのREADMEを参考に行いました。

導入時は testing library 以外に、 jest-domuser-eventjest-environment-jsdom も一緒にインストールしました。

導入詳細

React Testing Libraryのインストール

v13以上を使うには React v18 以上が必要ですが、既存のバージョンはv16系だったため、今回は v12 にしました。
v13以上を使うのに React v18 以上が必要なのは、リリースノートに記載されてました。
また、github上のREADMEにも、React 17以下の場合はv12を推奨しておりました。

jest-domのインストール

こちらのライブラリは、DOM用のマッチャーを拡張するためにインストールしました。
リリースノートで確認した限り、react testing library v12 でも最新バージョンのjest-domは動きそうだったため、今回はその時最新バージョンだった v6.4.2 をインストールしました。

user-eventのインストール

デフォルトで内蔵されている fireEvent ではなく、 user-event を使うためにインストールしました。
以下の理由から、 user-event を選択しました。

  1. 直感的に書ける。
  2. 公式ドキュメントuser-event が推奨されている。
  3. react-testing-library開発者が自身のブログで推奨している。
  4. fireEventではなく、user-eventを推奨するlintもあるためコミュニティ全体でuser-eventの方が好まれている
  5. パフォーマンスはfireEventの方が高いですが、ユーザーの動作を忠実に再現できる。(状況によっては fireEvent 使わざるをえないケースもありそうですが基本的には user-event が良さそう)

こちらも最新バージョンで問題なかったためその時の最新バージョンだった v14.5.2 をインストールしました。

jest-environment-jsdomのインストール

React Testing Library でシミュレーションを行うために必要だったため、インストールしました。
(Jest v28からjs-domがデフォルトでインストールされなくなったのでインストールが必要)
インストールしたバージョンは、今回インストールした Jest のバージョンと同じ v29.7.0 をインストールしました。

その他(型チェック)

その他の設定として、テストファイルで型チェックするための設定も追加しました。
jsx, tsxファイルをテストするだけなら jest.config.js に以下のtransformでの設定を書けば実行は可能ではあります。

module.exports = {
    transform: {
       '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest'
    },
};

しかしこれだと型のチェックまでは行ってくれないので、 tsconfig.json ファイルでの設定にする必要があり、以下の内容を追加しました。

{
  "compilerOptions": {
    "esModuleInterop": true,
    "jsx": "react"
  }
}

まとめ

以上でフロントテスト導入で行ったことのまとめを終わります。
導入にあたり、依存関係の確認や適切なバージョンの選定など普段あまり行ってこなかったことができて個人的にとても勉強になりました。
今後はテスト環境構築のみで終わらさないために、継続的にテストが書ける環境を整えて開発プロセスを効率化させたり、CI/CDにも組み込んでフロントテストの自動化も行えるようにして、生産性向上に繋げられるように引き続き頑張りたいと思います。

ゲームエイトテックブログ

Discussion