軟體測試對於確保應用程式按預期執行至關重要,尤其是在引入變更時。在開發早期捕捉並修復錯誤對於保持程式碼的彈性和高質量至關重要。
在眾多可用的 JavaScript 測試工具和框架中,Jest 是最受歡迎的工具和框架之一。作為 Meta 的產品,Jest 為 JavaScript 應用程式和使用 JavaScript 框架構建的應用程式提供了廣泛的測試功能。
讓我們來探討一下 Jest 框架、它的功能以及如何最好地將它整合到您的開發工作流程中。
什麼是 Jest?
Jest 是一個靈活易用的框架。除了核心的 JavaScript 測試功能外,它還提供配置和外掛,以支援測試 Babel、webpack、Vite、Parcel 或基於 TypeScript 的應用程式。
Jest 已被開發人員廣泛採用,並擁有一系列社羣構建和維護的外掛。它的突出特點是易於使用: JavaScript 測試不需要額外的配置或外掛。但您也可以使用一些額外的配置選項執行更高階的測試,如測試 JavaScript 框架。
如何為 JavaScript 專案設定 Jest
讓我們來探討如何在現有 JavaScript 專案中設定 Jest。
前提條件
要學習本教程,請確保您已具備以下條件:
安裝 Jest 軟體包
1. 如果您還沒有跟進本教程的專案,請將此倉庫作為起點。
starter-files
分支為您提供了一個基礎,以便在學習本教程的過程中構建應用程式。參考主分支檢視本教程的程式碼,並交叉檢查你的程式碼。
2. 要使用 npm 安裝 Jest,請在終端中進入專案目錄並執行以下命令:
npm install --save-dev jest
--save-dev
選項會告訴npm將軟體包安裝到 devDependencies
下,其中包含開發所需的依賴項。
配置 Jest
在 package.json 中配置 Jest
在 package.json 檔案中,新增一個名為 jest
的物件,其屬性如下所示:
{ … "jest": { "displayName": "Ecommerce", "globals": { "PROJECT_NAME": "Ecommerce TD" }, "bail": 20, "verbose": true }, }
測試期間,Jest 會搜尋該物件並應用這些配置。您可以在 Jest 的配置頁面檢視其他選項,但該物件的屬性包括
displayName
— Jest 將此屬性值作為標籤新增到測試結果中。globals
— 儲存一個物件值,用於定義測試環境中可用的全域性變數。bail
— 預設情況下,Jest 會執行所有測試並在結果中顯示錯誤。bail
會告訴 Jest 在設定的失敗次數後停止執行。verbose
— 設定為true
時,會在測試執行過程中顯示單個測試報告。
在配置檔案中配置 Jest
您還可以在 jest.config.js 檔案中配置 Jest。Jest 還支援 .ts、.mjs、.cjs 和 .json 副檔名。執行測試時,Jest 會查詢這些檔案,並應用找到的檔案中的設定。
例如,請看這個 jest.config.js 檔案:
const config = { displayName: "Ecommerce", globals: { "PROJECT_NAME": "Ecommerce TD" }, bail: 20, verbose: true } module.exports = config;
該程式碼匯出了一個 Jest 配置物件,其屬性與上一示例相同。
你也可以使用一個包含可序列化 JSON 配置物件的自定義檔案,並在執行測試時將檔案路徑傳遞給 --config
選項。
建立基本測試檔案
配置好 Jest 後,就可以建立測試檔案了。Jest 會審查專案的測試檔案,執行它們並提供結果。測試檔案通常採用 [name].test.js 或 [name]-test.js 等格式。這種模式便於 Jest 和你的團隊識別測試檔案。
請看包含以下程式碼的 string-format.js 檔案:
function truncate( str, count, withEllipsis = true ) { if (str.length < = count) return str const substring = str.substr(0, count) if (!withEllipsis) return substring return substring + '...' } module.exports = { truncate }
函式 truncate()
將字串截斷到特定長度,並可選擇新增省略號。
編寫測試
1. 建立一個名為 string-format.test.js 的測試檔案。
2. 為使檔案井然有序,請將 string-format.test.js 檔案放置在與 string-format.js 檔案相同的目錄下,或放置在特定的測試目錄下。無論測試檔案在專案的哪個位置,Jest 都能找到並執行它。使用 Jest,您可以在各種情況下測試應用程式。
3. 在 string-format.test.js 中編寫一個基本測試,如下所示:
const { truncate } = require('./string-format') test('truncates a string correctly', () = > { expect(truncate("I am going home", 6)).toBe('I am g...') })
測試用例描述正確截斷了一個字串 truncates a string correctly
。這段程式碼使用了 Jest 提供的 expect
函式,該函式用於測試值是否與預期結果相匹配。
程式碼將 truncate("I am going home", 6)
作為引數傳遞給 expect
。這段程式碼將測試使用引數 "I am going home"
和 6
呼叫 truncate
所返回的值。 expect
呼叫會返回一個 expectation 物件,它提供了對 Jest 匹配的訪問。
它還包含以 "I am g..."
為引數的 toBe
匹配器。 toBe
匹配器測試期望值和實際值是否相等。
執行測試
要執行測試,請定義 jest
命令。
1. 在專案的 package.json 檔案中,新增此 test
指令碼:
"scripts": { "test": "jest" }
2. 現在在終端執行 npm run test
、 npm test
或 npm t
。它將執行專案的 Jest。
執行測試後,結果就是這樣:
string-format.test.js 的 Jest 測試結果成功。
結果顯示了一個測試套件(string-format.test.js 檔案)、一個成功執行的測試("truncates a string correctly"
)以及在配置中定義的 displayName
(Ecommerce
)。
3. 在 string-format.js 中,如果新增一個額外的句點來破壞程式碼並執行測試,則會失敗:
由於截斷函式被破壞,Jest 測試結果失敗。
這一結果表明你破壞了 truncate
函式或進行了更新,需要更新測試。
如何使用 Jest 編寫測試
Jest 測試語法
Jest 的專有語法簡單易用。Jest 為您的專案提供全域性方法和物件,用於編寫測試。它的一些基本術語包括: describe
(描述)、 test
(測試)、 expect
(期望)和 matchers(匹配器)。
describe
: 該函式將相關測試組合到一個檔案中。test
: 該函式用於執行測試。這是it
的別名。它包含要測試的值的斷言。expect
: 該函式為各種值宣告斷言。它為各種形式的斷言提供了匹配器。- Matchers: 它們可以讓你以各種方式斷言值。你可以斷言值相等、布林相等和上下文相等(如陣列是否包含該值)。
要使用匹配器,請參考下面的示例:
1. 用以下程式碼替換 string-format.test.js 檔案中的測試:
describe("all string formats work as expected", () = > { test("truncates a string correctly", () = > { expect( truncate("I am going home", 6) ).toBe("I am g...") }) })
2. 執行程式碼。
結果如下:
成功的 Jest 測試結果顯示了描述標籤。
截圖顯示, describe
函式中的標籤建立了一個塊。儘管 describe
是可選項,但將測試分組到一個檔案中並提供更多上下文資訊還是很有幫助的。
在測試套件中組織測試
在 Jest 中,測試用例由 test
測試函式、 expect
期望函式和匹配器組成。相關測試用例的集合就是測試套件。在前面的示例中,string-format.test.js 就是一個測試套件,由一個測試用例組成,用於測試 string-format.js 檔案。
假設專案中還有更多檔案,如 file-operations.js、api-logger.js 和 number-format.js。您可以為這些檔案建立測試套件,如 file-operations.test.js、api-logger.test.js 和 number-format.test.js。
使用 Jest 匹配器編寫簡單斷言
我們已經探討了使用 toBe
匹配器的示例。使用其他 Jest 匹配器的斷言包括:
toEqual
— 用於測試物件例項中的 “深度” 等價性。toBeTruthy
— 用於測試一個布林值是否為真。toBeFalsy
— 用於測試一個布林值是否為假。toContain
— 用於測試陣列是否包含一個值。toThrow
— 用於測試呼叫的函式是否丟擲錯誤。stringContaining
— 用於測試字串是否包含子串。
讓我們來看看使用其中一些匹配器的示例。
例如,您可能希望函式或程式碼返回一個具有特定屬性和值的物件。
1. 請使用下面的程式碼片段測試這一功能。在這種情況下,您需要斷言返回的物件等於預期物件。
expect({ name: "Joe", age: 40 }).toBe({ name: "Joe", age: 40 })
本例使用 toBe
。測試失敗的原因是這個匹配器沒有檢查深度相等–它檢查的是值,而不是所有屬性。
2. 使用 toEqual
匹配器檢查深度相等:
expect({ name: "Joe", age: 40 }).toEqual({ name: "Joe", age: 40 })
這個測試通過了,因為兩個物件都 “深度相等”,也就是說它們的所有屬性都相等。
3. 試試另一個匹配器示例,測試定義的陣列是否包含特定元素。
expect(["orange", "pear", "apple"]).toContain("mango")
這個測試失敗的原因是 toContain
斷言 [ "orange"
、 "pear"
、 "apple"
] 陣列包含預期值 "mango"
,但陣列並不包含。
4. 使用變數進行與下面程式碼相同的測試:
const fruits = ["orange", "pear", "apple"]; const expectedFruit = "mango"; expect(fruits).toContain(expectedFruit)
測試非同步程式碼
到目前為止,我們已經測試了同步程式碼–在程式碼執行下一行之前返回值的表示式。您也可以使用 Jest 的 async
、 await
或 Promises 來處理非同步程式碼。
例如,apis.js 檔案中有一個用於發出 API 請求的函式:
function getTodos() { return fetch('https://jsonplaceholder.typicode.com/todos/1') }
getTodos
函式向 https://jsonplaceholder.typicode.com/todos/1
傳送 GET
請求。
1. 用以下程式碼建立名為 apis.test.js 的檔案,以測試偽造的 API:
const { getTodos } = require('./apis') test("gets a todo object with the right properties", () = > { return getTodos() .then((response) = > { return response.json() }) .then((data) = > { expect(data).toHaveProperty('userId') expect(data).toHaveProperty('id') expect(data).toHaveProperty('title') expect(data).toHaveProperty('completed') expect(data).toHaveProperty('description') }) })
該測試用例呼叫了 getTodos
函式,以獲取一個 todo
物件。解析 Promise 時,會使用 .then
方法獲取解析後的值。
在該值中,程式碼返回 response.json()
,這是另一個將響應轉換為 JSON 格式的 Promise。另一個 .then
方法會獲取包含 expect
和匹配器的 JSON 物件。程式碼斷言 JSON 物件包含五個屬性: userId
、 id
、 title
、 completed
和 description
。
2. 執行測試:
顯示非同步程式碼測試失敗的 Jest 測試結果。
如截圖所示, getTodos()
測試失敗。它期望得到 description
屬性,但 API 並沒有返回它。有了這些資訊,您就可以要求公司的 API 管理團隊在應用程式需要時加入該屬性,或者更新測試以滿足 API 的響應。
3. 刪除 description
屬性的斷言並重新執行測試:
Jest 測試結果顯示非同步程式碼測試通過。
截圖顯示一切都通過了測試。
4. 現在嘗試使用 async/await
代替傳統的 Promise 處理:
test("gets a todo object with the right properties", async () = > { const response = await getTodos() const data = await response.json() expect(data).toHaveProperty("userId") expect(data).toHaveProperty("id") expect(data).toHaveProperty("title") expect(data).toHaveProperty("completed") })
現在, async
關鍵字位於函式之前。程式碼在 getTodos()
之前使用了 await
,在 response.json()
之前使用了 await
。
高階 Jest 功能
模擬函式和模組
在編寫測試時,您可能希望測試一個具有外部依賴性的表示式。在某些情況下,尤其是單元測試,您的單元測試應與外部效應隔離。在這種情況下,您可以用 Jest 模擬您的函式或模組,以便更好地控制測試。
1. 例如,請看包含以下程式碼的 functions.js 檔案:
function multipleCalls(count, callback) { if (count < 0) return; for (let counter = 1; counter <= count; counter++) { callback() } }
multipleCalls
函式根據 count
的值執行。它取決於回撥函式–外部依賴關係。其目的是瞭解 multipleCalls
是否正確執行了外部依賴關係。
2. 要在測試檔案 functions.test.js 中模擬外部依賴關係並跟蹤依賴關係的狀態,請使用以下程式碼:
const { multipleCalls } = require('./functions') test("functions are called multiple times correctly", () => { const mockFunction = jest.fn() multipleCalls(5, mockFunction) expect( mockFunction.mock.calls.length ).toBe(5) })
在這裡, jest
物件的 fn
方法建立了一個模擬函式。然後,程式碼將 5
和 mock 函式作為引數傳遞,執行 multipleCalls
。然後,斷言 mockFunction
被呼叫了 5 次。 mock
屬性包含程式碼如何呼叫函式和返回值的資訊。
3. 執行測試時,這就是預期結果:
使用模擬函式的成功 Jest 測試結果。
如圖所示,程式碼呼叫了五次 mockFunction
。
在程式碼中,mock 函式模仿了外部依賴關係。當應用程式在生產中使用 multipleCalls
時,外部依賴關係是什麼並不重要。單元測試並不關心外部依賴關係是如何工作的。它只是驗證 multipleCalls
是否按預期執行。
4. 要模擬模組,請使用 mock
方法並傳遞一個檔案路徑,即模組:
const { truncate, } = require("./string-format") jest.mock("./string-format.js")
這段程式碼模仿 string-format.js 輸出的所有函式,並跟蹤其呼叫頻率。模組的 truncate
變成了一個 mock 函式,這會導致函式失去其原始邏輯。你可以從 truncate.mock.calls.length
屬性中瞭解測試中 truncate
的執行次數。
如果出現錯誤或程式碼無法執行,請將程式碼與完整的實現進行比較。
使用 Jest 和 React 測試庫測試 React 元件
如果您還沒有專案來跟進本教程,可以使用 React 示例專案作為起點。 starter-files
分支可以幫助您在學習本教程的過程中開始編寫程式碼。將主分支作為參考,對照本教程的完整程式碼檢查您的程式碼。
您可以使用 Jest 測試 React 等 JavaScript 框架。當你使用 Create React App 建立 React 專案時,它們會立即支援 React 測試庫和 Jest。如果不使用 Create React App 建立 React 專案,則需要安裝 Jest,以便使用 Babel 和 React 測試庫測試 React。如果克隆 starter-app
分支,則無需安裝依賴項或應用配置。
1. 如果使用示例專案,請使用此命令安裝所需的依賴項:
npm install --save-dev babel-jest @babel/preset-env @babel/preset-react react-testing-library
您也可以使用 Enzyme 代替 React Testing Library。
2. 更新 babel.config.js 中的 Babel 配置,如果該檔案不存在,則建立該檔案:
module.exports = { presets: [ '@babel/preset-env', ['@babel/preset-react', {runtime: 'automatic'}], ], };
3. 請看包含以下程式碼的 src/SubmitButton.js 檔案:
import React, { useState } from 'react' export default function SubmitButton(props) { const {id, label, onSubmit} = props const [isLoading, setisLoading] = useState(false) const submit = () => { setisLoading(true) onSubmit() } return
該 SubmitButton
元件接收三個道具:
id
— 按鈕的識別符號。label
— 要在按鈕中顯示的文字。onSubmit
— 當有人點選按鈕時觸發的函式。
程式碼將 id
屬性分配給 data-testid
屬性,該屬性用於標識測試元素。
該元件還會跟蹤 isLoading
狀態,並在有人點選按鈕時將其更新為 true
。
4. 為該元件建立測試。將以下程式碼放入 SubmitButton.test.js 檔案中:
import {fireEvent, render, screen} from "@testing-library/react" import "@testing-library/jest-dom" import SubmitButton from "./SubmitButton" test("SubmitButton becomes disabled after click", () => { const submitMock = jest.fn() render( <SubmitButton id="submit-details" label="Submit" onSubmit={submitMock} / > ) expect(screen.getByTestId("submit-details")).not.toBeDisabled() fireEvent.submit(screen.getByTestId("submit-details")) expect(screen.getByTestId("submit-details")).toBeDisabled() })
上面的程式碼渲染了 SubmitButton
元件,並使用 screen.getByTestId
查詢方法通過 data-testid
屬性獲取 DOM 節點。
第一個 expect
是 getByTestId("submit-details")
,並使用 not
修飾符和 toBeDisabled
匹配器(從 react-testing-library
暴露)來斷言按鈕未被禁用。在每個匹配器中都使用 not 修飾符,以斷言匹配器的對立面。
然後,程式碼會觸發元件上的 submit
事件,並檢查按鈕是否禁用。你可以在測試庫文件中找到更多自定義匹配器。
5. 現在,執行測試。如果克隆了 starter-files
分支,請在開始測試前執行 npm install
,確保安裝了所有專案依賴項。
顯示 react 元件測試通過的 Jest 測試結果。
執行程式碼覆蓋率報告
Jest 還提供程式碼覆蓋率報告,可顯示專案的測試範圍。
1. 向 Jest 傳遞 --coverage
選項。在 package.json 中(JavaScript 專案中)的 Jest 指令碼中,使用此覆蓋率選項更新 Jest 命令:
"scripts": { "test": "jest --coverage" }
2. 執行 npm run test
測試程式碼。你會得到如下報告:
每個測試服的成功 Jest 覆蓋率報告。
該報告顯示,Jest 已 100% 測試了 SubmitButton.js 和 string-format.js 中的函式。它還顯示 Jest 未測試 string-format.js 中的任何語句和行。測試覆蓋率顯示,string-format.js 中未覆蓋的行是第 7 行和第 12 行。
在第 7 行, truncate
函式中的 return str
沒有執行,因為 if (str.length <= count)
條件返回 false
。
在第 12 行,同樣是在 truncate
函式中,return substring
不執行,因為 if (!withEllipsis)
條件返回 false
。
將 Jest 與您的開發工作流程相結合
讓我們看看如何整合這些測試來改進開發工作流程。
在觀察模式下執行測試
你可以在更改程式碼時使用觀察模式自動執行測試,而不是手動執行測試。
1. 要啟用觀察模式,請更新 package.json(JavaScript 專案)中的 Jest 命令指令碼,新增 --watchAll
選項:
"scripts": { "test": "jest --coverage --watchAll" }
2. 執行 npm run test
。它將在觀察模式下觸發 Jest:
以觀察模式執行 Jest
每次更改專案時都會執行測試。這種方法有助於在構建應用程式時獲得持續反饋。
設定預提交鉤子
在 Git 環境中,只要發生特定事件(如拉、推或提交),鉤子就會執行指令碼。預提交鉤子定義了哪些指令碼會在預提交事件(程式碼在提交前觸發的事件)中執行。
只有指令碼不出錯,提交才會成功。
在預提交前執行 Jest 可確保在提交前沒有任何測試失敗。
您可以使用各種庫在專案中設定 git 掛鉤,例如 ghooks。
1. 在 devDependencies
下安裝 ghooks
:
npm install ghooks --save-dev
2. 在 package.json 檔案(JavaScript 專案)的頂層新增 configs
物件。
3. 在 configs
下新增一個 ghooks
物件。
4. 新增一個關鍵字為 pre-commit
、值為 jest
的屬性。
{ … "config": { "ghooks": { "pre-commit": "jest" } }, }
5. 提交程式碼 程式碼會觸發預提交鉤子,執行 Jest:
使用 ghooks 在預提交期間執行 Jest。
小結
現在您知道如何將 Jest 整合到您的開發工作流中,以便在您進行更改時自動執行。這種方法可提供持續反饋,因此您可以在將更改釋出到生產環境之前快速修復任何程式碼問題。
評論留言