Maison  >  Article  >  interface Web  >  Faisons fonctionner Jest beaucoup plus rapidement

Faisons fonctionner Jest beaucoup plus rapidement

Patricia Arquette
Patricia Arquetteoriginal
2024-10-31 12:23:54464parcourir

Let’s Make Jest Run Much Faster

Mais d'abord, nous devons comprendre pourquoi c'est si lent.

Exemple pratique

Considérons un simple composant 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>
  );
}

Le composant de l'application ne dépend que d'une seule fonction utilitaire - deepClone. Le fichier utils ressemble à ceci.

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;

Il exporte trois fonctions d'assistance sur une ligne. C'est tout.
Maintenant, voici une grande question : combien de temps pensez-vous qu'il faudra pour exécuter ce test ?

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 />);
});

La réponse ? Une éternité !

 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

Il a fallu 5 secondes sur ma machine pour exécuter un scénario de test à une seule ligne pour un composant React à une seule ligne.

Analyse des performances

Pour analyser ce qui se passe dans les coulisses, nous pouvons utiliser le profileur de Chrome - Je vous recommande de regarder cette vidéo perspicace de Kent C. Dodds.
Alternativement, vous pouvez utiliser une bibliothèque jest-neat-runner, qui simplifie le processus de profilage. Définissez l'option NEAT_REPORT_MODULE_LOAD_ABOVE_MS sur 150 et activez NEAT_REPORT_TRANSFORM. Cette configuration imprimera les modules qui prennent plus de 150 ms à charger et fournira des informations sur le temps qu'il a fallu pour traiter (ouvrir et transpiler) les fichiers.

Utilisons ce dernier. C'est le résultat.

> 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

On charge la bibliothèque "@mui/material" pendant presque 2 secondes sans même l'utiliser !

Cause fondamentale de nombreux projets ?

Dépendances désordonnées

D'après mon expérience, les problèmes de performances avec jest proviennent principalement du grand nombre de dépendances transitives qui ne sont même pas utilisées au moment de l'exécution. Comme le montre notre exemple ci-dessus, si vous ne prêtez pas suffisamment attention aux fichiers que vous importez dans votre application, vous pourriez vous retrouver dans la même situation que moi.

Dans mon cas, le composant App dépend uniquement de la fonction utilitaire deepClone. Cependant, puisque deepClone est exporté à partir du fichier utils, toutes les dépendances du fichier utils ont également été chargées avec celui-ci.

Les fichiers contenant de nombreuses fonctions vaguement liées et de lourdes dépendances peuvent ralentir considérablement votre application et vos tests.

Fichiers de baril

Jest n'est pas un ami des modules ESM, ce qui l'amène à se replier sur CommonJS. Par conséquent, le tremblement d’arbre ne fonctionne pas correctement. Ceci est particulièrement problématique lorsqu'on s'appuie sur des modules importés à partir de fichiers Barrel (fichiers d'index).
Par exemple, lorsque vous importez un petit composant ou une fonction à partir d'un fichier Barrel, Jest chargera également tout le reste - , ce qui entraîne évidemment une surcharge inutile.

Et maintenant ?

Ajuster manuellement la stratégie d'importation

En plus de supprimer les fichiers Barrel et de refactoriser l'intégralité de la base de code en divisant les fichiers avec de nombreuses dépendances en modules plus petits et plus ciblés. Nous pouvons identifier les modules qui prennent beaucoup de temps à charger et rechercher des modules alternatifs plus petits ou vérifier si le module importé exporte des pièces individuelles séparément (c'est-à-dire des importations nommées) au lieu d'utiliser le fichier baril.
C'est-à-dire, au lieu de

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>
  );
}

faire

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;

Si nous n'utilisons pas du tout le module, nous pouvons le simuler via jest.mock pour éviter de le charger complètement.
Cependant, ces ajustements peuvent prendre beaucoup de temps.

Approche du cache d'exécution

Une méthode plus efficace consiste à utiliser la bibliothèque jest-neat-runner avec l'option NEAT_RUNTIME_CACHE. Lorsque cette option est activée, la bibliothèque suit l'utilisation réelle de tous les modules (par fichier de test) et stocke les dépendances dont nous n'avons pas besoin pour les tests ultérieurs dans un cache. Laissez-moi vous montrer ce qu'il fait sur l'exemple ci-dessus

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 />);
});

Nous avons réduit le temps d'exécution de cinq secondes à deux en sautant le chargement de 26 bibliothèques inutiles, dont la bibliothèque MUI.
Soyez prudent - il y a plusieurs mises en garde lors de l'utilisation de NEAT_RUNTIME_CACHE, alors assurez-vous de lire le README avant de l'utiliser.

Autres techniques d'optimisation

Optimisations de la transpilation : examinez le nombre de fichiers qui doivent être transpilés et utilisez le transpileur le plus efficace (comme SWC ou esbuild). Si vous souhaitez gagner du temps, l'option NEAT_REPORT_TRANSFORM de jest-neat-runner fournira des informations détaillées sur le temps et le nombre de modules nécessaires pour transpiler.

Mise en cache des modules en mémoire : Par défaut, Jest ne met pas en cache les modules en mémoire, ce qui signifie que chaque exécution de test doit ouvrir, analyser et charger le module en mémoire. Si vous disposez d'une vaste suite de tests et de suffisamment de mémoire, envisagez d'utiliser l'option NEAT_TRANSFORM_CACHE pour accélérer les choses.

Qu’en est-il du pipeline CI ?

Exécutions parallèles : CircleCI et GitHub Actions prennent en charge les exécutions parallèles. Cela signifie que vous pouvez faire tourner plus de machines et diviser la charge à l'aide du paramètre shard dans Jest.
Stockage du cache Jest et Neat : ceci est crucial pour tirer parti de Jest et de Jest-neat-runner dans le CI. Assurez-vous de définir l'option cacheDirectory dans Jest. Ensuite, stockez le répertoire après l’exécution du test et restaurez le cache avant d’exécuter les tests. Attention : si vous utilisez le parallélisme, assurez-vous de stocker des caches uniques pour chaque nœud. Par exemple, CircleCI expose la variable d'environnement CIRCLE_NODE_INDEX, que vous pouvez exploiter lors du stockage du cache. Voici à quoi cela ressemble dans le 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>
  );
}

En suivant ces directives, vous pouvez améliorer considérablement les performances de Jest dans vos projets.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn