但首先,我们需要了解为什么它这么慢。
考虑一个简单的 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> ); }
应用程序组件仅依赖于一个实用函数 - 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;
它导出三个单行辅助函数。就是这样。
现在,有一个大问题:您认为执行此测试需要多长时间?
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
在我的机器上花了 5 秒来为单行 React 组件执行单行测试用例。
要分析幕后发生的事情,我们可以使用 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 实用函数。但是,由于 deepClone 是从 utils 文件导出的,因此 utils 文件中的所有依赖项也会随之加载。
包含大量松散相关函数和严重依赖项的文件可能会显着减慢您的应用程序和测试速度。
Jest 不适合 ESM 模块,这导致它回退到 CommonJS。因此,tree-shaking 无法正常工作。当依赖从桶文件(索引文件)导入的模块时,这尤其成问题。
例如,当您从桶文件导入一个小组件或函数时,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 来模拟它,以避免完全加载它。
然而,这些调整可能相当耗时。
更有效的方法是将 jest-neat-runner 库与 NEAT_RUNTIME_CACHE 选项一起使用。当此选项打开时,库会跟踪所有模块(每个测试文件)的实际运行时使用情况,并将后续测试运行不需要的依赖项存储到缓存中。让我在上面的示例中展示它的作用
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 />); });
我们通过跳过 26 个不必要的库(包括 MUI 库)的加载,将执行时间从 5 秒减少到 2 秒。
请小心 - 使用 NEAT_RUNTIME_CACHE 时有一些注意事项,因此请务必在使用前阅读 README。
转译优化:检查需要转译的文件数量并使用最有效的转译器(例如 SWC 或 esbuild)。如果您想节省时间,jest-neat-runner 中的 NEAT_REPORT_TRANSFORM 选项将提供有关转译所需时间和模块数量的详细信息。
在内存中缓存模块:默认情况下,Jest 不会在内存中缓存模块,这意味着每次测试运行都必须打开、解析并将模块加载到内存中。如果您有大量测试和足够的内存,请考虑使用 NEAT_TRANSFORM_CACHE 选项来加快速度。
并行运行:CircleCI 和 GitHub Actions 支持并行运行。这意味着您可以启动更多机器并使用 Jest 中的分片参数划分负载。
存储 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中文网其他相关文章!