但首先,我們要了解為什麼它這麼慢。
考慮一個簡單的 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中文網其他相關文章!