ホームページ  >  記事  >  ウェブフロントエンド  >  Jest をもっと速く実行してみましょう

Jest をもっと速く実行してみましょう

Patricia Arquette
Patricia Arquetteオリジナル
2024-10-31 12:23:54464ブラウズ

Let’s Make Jest Run Much Faster

しかしまず、なぜこれほど遅いのかを理解する必要があります。

実践例

単純な React コンポーネントを考えてみましょう。

import React from "react";
import { deepClone } from "./utils";

export function App() {
  const obj = { foo: 'bar' };

  return (
    <div>
      <p>Object looks like this: {JSON.stringify(deepClone(obj))}</p>
    </div>
  );
}

アプリ コンポーネントは 1 つのユーティリティ関数 - deepClone にのみ依存します。 utils ファイルは次のようになります。

import _ from 'lodash';
import moment from 'moment';
import * as mui from '@mui/material';

export const deepClone = (obj) => _.cloneDeep(obj);
export const getFormattedDate = (date) => moment(date).format('YYYY-MM-DD');

export const isButton = (instance) => instance === mui.Button;

3 つの 1 行ヘルパー関数をエクスポートします。以上です。
ここで大きな質問があります: このテストの実行にはどれくらい時間がかかると思いますか?

import React from "react";
import { render, screen } from "@testing-library/react";
import { App } from "./app";
import "@testing-library/jest-dom";

test("renders the app", () => {
  render(<App />);
});

答えは?永遠です!

 PASS  src/tests/react-app/react-app.test.js
  √ renders the date and sum correctly (25 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.045 s

私のマシンでは、ワンライナー React コンポーネントのワンライナー テスト ケースを実行するのに 5 秒かかりました。

パフォーマンスの分析

舞台裏で何が起こっているかを分析するには、Chrome のプロファイラーを使用できます - Kent C. Dodds によるこの洞察力に富んだビデオを見ることをお勧めします。
あるいは、プロファイリング プロセスを簡素化する jest-neat-runner ライブラリを使用することもできます。 NEAT_REPORT_MODULE_LOAD_ABOVE_MS オプションを 150 に設定し、NEAT_REPORT_TRANSFORM を有効にします。この設定は、ロードに 150 ミリ秒以上かかるモジュールを出力し、ファイルの処理 (オープンとトランスパイル) に要した時間に関する情報を提供します。

後者を使用しましょう。これが出力です。

> jest src/tests/react-app/

From src\tests\react-app\utils.js -> @mui/material in 1759ms
From node_modules\@mui\material\node\styles\adaptV4Theme.js -> @mui/system in 509ms
From src\tests\react-app\react-app.test.js -> @testing-library/react in 317ms
From node_modules\@testing-library\react\dist\pure.js -> @testing-library/dom in 266ms
From node_modules\@mui\system\ThemeProvider\ThemeProvider.js -> @mui/private-theming in 166ms
From node_modules\@testing-library\dom\dist\role-helpers.js -> aria-query in 161ms

「@mui/material」ライブラリを使用せずに、ほぼ 2 秒間ロードしています!

多くのプロジェクトの根本原因?

乱雑な依存関係

私の経験では、jest のパフォーマンスの問題は主に、実行時にも使用されない多数の推移的な依存関係に起因します。上の例で示したように、アプリケーションにどのファイルをインポートするかに十分な注意を払わないと、私と同じ状況に陥る可能性があります。

私の場合、App コンポーネントは deepClone ユーティリティ関数にのみ依存します。ただし、deepCloneutils ファイルからエクスポートされるため、utils ファイル内のすべての依存関係も一緒にロードされました。

関連性の低い関数や重い依存関係が多数含まれるファイルは、アプリケーションやテストの速度を大幅に低下させる可能性があります。

バレルファイル

Jest は ESM モジュールと友達ではないため、CommonJS にフォールバックします。その結果、ツリーシェイキングが正しく機能しなくなります。これは、バレル ファイル (インデックス ファイル) からインポートされたモジュールに依存する場合に特に問題になります。
たとえば、バレル ファイルから小さなコンポーネントや関数をインポートすると、Jest は他のすべても同様にロードします - これにより、明らかに不必要なオーバーヘッドが発生します。

今何?

インポート戦略を手動で調整する

バレル ファイルを削除し、多数の依存関係を持つファイルをより小さく、より焦点を絞ったモジュールに分割することで、コードベース全体をリファクタリングする以外にも。ロードに時間がかかるモジュールを特定して、より小さい代替モジュールを探したり、インポートされたモジュールがバレル ファイルを使用する代わりに個々の部分を個別にエクスポートするかどうか (つまり、名前付きインポート) を確認したりできます。
つまり、
の代わりに

import React from "react";
import { deepClone } from "./utils";

export function App() {
  const obj = { foo: 'bar' };

  return (
    <div>
      <p>Object looks like this: {JSON.stringify(deepClone(obj))}</p>
    </div>
  );
}

してください

import _ from 'lodash';
import moment from 'moment';
import * as mui from '@mui/material';

export const deepClone = (obj) => _.cloneDeep(obj);
export const getFormattedDate = (date) => moment(date).format('YYYY-MM-DD');

export const isButton = (instance) => instance === mui.Button;

モジュールをまったく使用していない場合は、jest.mock を介してモジュールをモックし、完全にロードされるのを避けることができます。
ただし、これらの調整には非常に時間がかかる場合があります。

ランタイムキャッシュのアプローチ

より効果的な方法には、NEAT_RUNTIME_CACHE オプションを指定して jest-neat-runner ライブラリを使用することが含まれます。このオプションがオンの場合、ライブラリはすべてのモジュール (テスト ファイルごと) の実際の実行時使用状況を追跡し、後続のテスト実行に必要のない依存関係をキャッシュに保存します。上の例でそれが何を行うかを示しましょう

import React from "react";
import { render, screen } from "@testing-library/react";
import { App } from "./app";
import "@testing-library/jest-dom";

test("renders the app", () => {
  render(<App />);
});

MUI ライブラリを含む 26 個の不要なライブラリのロードをスキップすることで、実行時間を 5 秒から 2 秒に短縮しました。
注意してください - NEAT_RUNTIME_CACHE を使用する場合はいくつかの注意事項があるため、使用する前に必ず README を読んでください。

その他の最適化手法

トランスパイルの最適化: トランスパイルする必要があるファイルの数を調べ、最も効果的なトランスパイラー (SWC や esbuild など) を使用します。時間を節約したい場合は、jest-neat-runner の NEAT_REPORT_TRANSFORM オプションを使用すると、トランスパイルにかかる時間とモジュールの数に関する詳細情報が提供されます。

メモリ内のモジュールのキャッシュ: デフォルトでは、Jest はモジュールをメモリにキャッシュしません。つまり、すべてのテスト実行でモジュールを開いて解析し、メモリにロードする必要があります。膨大なテストスイートと十分なメモリがある場合は、NEAT_TRANSFORM_CACHE オプションを使用して処理を高速化することを検討してください。

CI パイプラインについてはどうですか?

並列実行: CircleCI と GitHub Actions は並列実行をサポートします。これは、Jest の shard パラメーターを使用して、より多くのマシンを起動し、負荷を分割できることを意味します。
Jest および Neat キャッシュの保存: これは、CI で Jest および jest-neat-runner を利用するために重要です。 Jest で cacheDirectory オプションを必ず設定してください。次に、テストの実行後にディレクトリを保存し、テストを実行する前にキャッシュを復元します。注意: 並列処理を使用している場合は、ノードごとに一意のキャッシュを保存するようにしてください。たとえば、CircleCI は CIRCLE_NODE_INDEX 環境変数を公開しており、キャッシュを保存するときにこれを利用できます。 CircleCI では次のようになります。

import React from "react";
import { deepClone } from "./utils";

export function App() {
  const obj = { foo: 'bar' };

  return (
    <div>
      <p>Object looks like this: {JSON.stringify(deepClone(obj))}</p>
    </div>
  );
}

これらのガイドラインに従うことで、プロジェクトにおける Jest のパフォーマンスを大幅に向上させることができます。

以上がJest をもっと速く実行してみましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。