全面掌握 Jest:從零開始的測試指南(上篇)

来源:https://www.cnblogs.com/vigourice/p/18416692
-Advertisement-
Play Games

隨著JavaScript在前後端開發中的廣泛應用,測試已成為保證代碼質量的關鍵環節。 為什麼需要單元測試 在我們的開發過程中,經常需要定義一些演算法函數,例如將介面返回的數據轉換成UI組件所需的格式。為了校驗這些演算法函數的健壯性,部分開發同學可能會手動定義幾個輸入樣本進行初步校驗,一旦校驗通過便不再深 ...


隨著JavaScript在前後端開發中的廣泛應用,測試已成為保證代碼質量的關鍵環節。

為什麼需要單元測試

在我們的開發過程中,經常需要定義一些演算法函數,例如將介面返回的數據轉換成UI組件所需的格式。為了校驗這些演算法函數的健壯性,部分開發同學可能會手動定義幾個輸入樣本進行初步校驗,一旦校驗通過便不再深究。

然而,這樣的做法可能會帶來一些潛在的問題。首先,邊界值的情況往往容易被忽視,導致校驗不夠全面,增加了系統出現故障的風險。其次,隨著需求的變化和演進,演算法函數可能需要進行優化和擴展。如果前期的校驗工作不夠徹底,不瞭解現有函數覆蓋的具體場景,就可能導致在後續的修改中引入新的問題。

單元測試可以有效地解決上述問題。在定義演算法函數時,同步創建單元測試文件,並將可能出現的各種場景逐一列舉。如果單元測試未能通過,項目在編譯時會直接報錯,從而能夠及時發現並針對性地解決問題。此外,當後續有新同學加入並需要擴展功能時,他們不僅需要在原有的單元測試基礎上添加新的測試用例,還能確保新功能的正確性,同時保障原有功能的正常運行。

自定義測試邏輯

在開始使用工具來進行單元測試之前,我們可以先自定義一個工具函數供測試使用。

例如,我們有一個 add 函數,期望它能夠正確計算兩個數的和,並驗證其結果是否符合預期。比如,我們希望驗證 2 + 3的結果是否等於 5 ,可以使用 expect(add(2, 3)).toBe(5) 這樣的代碼來實現。為此,我們可以自行定義一個expect 函數,使其具備類似Jest中 expect 函數的功能

function add(a, b) { return a + b; }
function expect(result) {
  return {
    toBe(value) {
      if (result === value) {
        console.log("驗證成功");
      } else {
        throw new Error(`執行錯誤:${result} !== ${value}`);
      }
    },
  };
}

// 調用示例
try {
  expect(add(2, 3)).toBe(5);  // 輸出:"驗證成功"
  expect(add(2, 3)).toBe(6);  // 拋出錯誤
} catch (err) {
  console.error(err.message);  // 輸出:"執行錯誤:5 !== 6"
}

為了使測試更具描述性和可讀性,我們可以進一步增強我們的測試邏輯。例如,我們可以添加一個 test 函數,用於描述測試的目的,併在測試失敗時提供更詳細的錯誤信息。

function test(description, fn) {
  try {
    fn();
    console.log(`測試通過: ${description}`);
  } catch (err) {
    console.error(`測試失敗: ${description} - ${err.message}`);
  }
}
// 調用示例
test("驗證 2 + 3 是否等於 5", () => {
  expect(add(2, 3)).toBe(5);
});
test("驗證 2 + 3 是否等於 6", () => {
  expect(add(2, 3)).toBe(6);
});

通過這種方式,我們模擬了一個簡單的測試用例,其中 testexpect 函數類似於Jest中的功能。然而,我們的自定義版本相對簡陋,缺乏 Jest 提供的豐富功能。

Jest

通過上述示例,我們可以瞭解到編寫測試的基本思路和方法。然而,在實際開發中,我們需要一個功能更加強大、易用性更高的測試工具。Jest 正是這樣一個工具,它不僅提供了豐富的匹配器(如toBe、toEqual等),還支持非同步測試Mock函數Snapshot測試 等功能。

引入 Jest 的依賴後,我們可以直接使用其內置的 testexpect 函數,從而大大提高測試的效率和準確性。Jest 的強大之處在於它能夠幫助我們全面地覆蓋各種測試場景,並提供詳細的錯誤報告,使我們能夠快速定位和解決問題。

初始化

首先,我們通過 npm install jest -D 安裝 Jest 依賴,然後執行 npx jest --init。此時,命令行工具會出現一系列互動式問答,詢問你是否要為 Jest 添加名為 test 的腳本指令、是否使用 TypeScript 作為配置文件、測試用例執行環境、是否需要代碼覆蓋率測試報告、生成測試報告的平臺的編譯器以及是否需要在每次測試用例執行前重置 Mock 函數狀態。

完成所有問答後,Jest 會修改 package.json 文件,並生成jest.config.js配置文件。在執行測試用例時,將依據這些配置項進行。

我們創建一個 math.test.js 文件,並將之前的測試代碼放入其中

function add(a, b) {
  return a + b;
}
test("測試 add 函數", () => {
  expect(add(2, 3)).toBe(5);
});

通過 npm run test 執行 Jest 運行指令,可以在命令行工具查看詳細的測試信息,包括哪個文件的哪條測試用例的狀態,以及簡易的測試覆蓋率報告。

在實際使用場景中,add 函數通常定義在項目文件中,並通過 ES 模塊化 (export 和 import) 方式導出和導入。預設情況下,Jest 並不支持 ES 模塊化語法,因此我們需要通過 Babel 進行配置。

首先,執行以下命令安裝 Babel 及其核心庫和預設

npm install @babel/core @babel/preset-env --save-dev

然後,創建babel.config.js文件並定義配置

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          node: "current",
        },
      },
    ],
  ],
};

接著,將 add 函數移到 math.js 文件中,並使用 export 導出

// math.js
export function add(a, b) {
  return a + b;
}

最後,在 math.test.js 文件中使用 import 導入

// math.test.js
import { add } from './math';
test("測試 add 函數", () => {
  expect(add(2, 3)).toBe(5);
});

通過以上步驟,你就完成了使用 Jest 執行 ES 模塊化代碼的環境初始化。

匹配器

Jest 中最常用的功能之一就是匹配器。在前面進行測試時,我們就接觸過 toBe 這一匹配器,它用於判斷值是否相等。除此之外,還有許多其他類型的匹配器。

值相等

判斷值相等有兩種匹配器:toBetoEqual。對於基本數據類型(如字元串、數字、布爾值),兩者的使用效果相同。但對於引用類型(如對象和數組),toBe 只有在兩個引用指向同一個記憶體地址時才會返回 true

const user = { name: "alice" };
const info = { name: "alice" };

test("toEqual", () => {
  expect(info).toEqual(user); // 通過,兩者結構相同
});
test("toBe", () => {
  expect(info).toBe(user); // 不通過,兩者的引用地址不同
});
是否有值

存在 toBeNulltoBeUndefinedtoBeDefined 匹配器來分別判斷值是否為 null、未定義或已定義。

test("toBeNull", () => {
  expect(null).toBeNull();
  expect(0).toBeNull(); // 不通過
  expect("hello").toBeNull(); // 不通過
  expect(undefined).toBeBull(); // 不通過
});

test("toBeUnDefined", () => {
  expect(null).toBeUndefined(); // 不通過
  expect(0).toBeUndefined(); // 不通過
  expect("hello").toBeUndefined(); // 不通過
  expect(undefined).toBeUndefined();
});

test("toBeDefined", () => {
  expect(null).toBeDefined();
  expect(0).toBeDefined();
  expect("hello").toBeDefined();
  expect(undefined).toBeDefined(); // 不通過
});
是否為真

toBeTruthy 用於判斷值是否為真,toBeFalsy 用於判斷值是否為假,not 用於取反。

test("toBeTruthy", () => {
  expect(null).toBeTruthy(); // 不通過
  expect(0).toBeTruthy(); // 不通過
  expect(1).toBeTruthy();
  expect("").toBeTruthy(); // 不通過
  expect("hello").toBeTruthy();
  expect(undefined).toBeTruthy(); // 不通過
});
test("toBeFalsy", () => {
  expect(null).toBeFalsy();
  expect(0).toBeFalsy();
  expect(1).toBeFalsy(); // 不通過
  expect("").toBeFalsy();
  expect("hello").toBeFalsy(); // 不通過
  expect(undefined).toBeFalsy();
});
test("not", () => {
  expect(null).not.toBeTruthy();
  expect("hello").not.toBeTruthy(); // 不通過
});
數字比較

toBeGreaterThan 用於判斷是否大於某個數值,toBeLessThan 用於判斷是否小於某個數值,toBeGreaterThanOrEqual 用於判斷是否大於或等於某個數值,toBeCloseTo 用於判斷是否接近某個數值(差值 < 0.005)。

test("toBeGreaterThan", () => {
  expect(9).toBeGreaterThan(5);
  expect(5).toBeGreaterThan(5); // 不通過
  expect(1).toBeGreaterThan(5); // 不通過
});

test("toBeLessThan", () => {
  expect(9).toBeLessThan(5); // 不通過
  expect(5).toBeLessThan(5); // 不通過
  expect(1).toBeLessThan(5);
});

test("toBeGreaterThanOrEqual", () => {
  expect(9).toBeGreaterThanOrEqual(5);
  expect(5).toBeGreaterThanOrEqual(5);
  expect(1).toBeGreaterThanOrEqual(5); // 不通過
});

test("toBeCloseTo", () => {
  expect(0.1 + 0.2).toBeCloseTo(0.3);
  expect(1 + 2).toBeCloseTo(3);
  expect(0.1 + 0.2).toBeCloseTo(0.4); // 不通過
});
字元串相關

toMatch 用於判斷字元串是否包含指定子字元串,部分包含即可。

test("toMatch", () => {
  expect("alice").toMatch("alice"); // 通過
  expect("alice").toMatch("lice"); // 通過
  expect("alice").toMatch("al"); // 通過
});
數組相關

toContain 用於判斷數組是否包含指定元素,類似於 JavaScript 中的 includes 方法。

test("toContain", () => {
  expect(['banana', 'apple', 'orange']).toContain("apple");
  expect(['banana', 'apple', 'orange']).toContain("app"); // 不通過
});
error相關

toThrow 用於判斷函數是否拋出異常,並可以指定拋出異常的具體內容。

test("toThrow", () => {
  const throwNewErrorFunc = () => {
    throw new TypeError("this is a new error");
  };
  expect(throwNewErrorFunc).toThrow();
  expect(throwNewErrorFunc).toThrow("new error");
  expect(throwNewErrorFunc).toThrow("TypeError"); // 不通過
});

以上就是各類型常用的匹配器。

命令行工具

package.json 中配置 script 指令,可以使 .test.js 文件在修改時實時自動執行測試用例。

"scripts": {
   "jest": "jest --watchAll"
},

在命令行中,你會實時看到當前測試用例的執行結果。同時,Jest 還提供了一些快捷配置,按下 w 鍵即可查看具體有哪些指令。

主要有以下幾種類型:

f 模式
在所有測試用例中,只執行上一次失敗的測試用例。即使其他測試用例的內容有修改,也不會被執行。

o 模式
只執行修改過的測試用例。這個功能需要配合 Git 來實現,根據本次相對於上次 Git 倉庫的更改。這種模式還可以通過配置 script 指令來實現,即:

"script": {
"test": "jest --watch"
}

p模式
當使用 --watchAll 時,修改一個文件的代碼後,所有的測試用例都會執行。進入 p 模式後,可以輸入文件名 matchersFile,此時修改任何文件只會去查找包含 matchersFile 的文件並執行。

t模式
輸入測試用例名稱,匹配 test 函數的第一個參數。匹配成功後即執行該測試用例。

q模式
退出實時代碼檢測。

通過不同的指令,你可以更有針對性地檢測測試用例。

鉤子函數

在 Jest 中,describe 函數用於將一系列相關的測試用例(tests)組合在一起,形成一個描述性的測試塊。它接受兩個參數:第一個參數是一個字元串,用於描述測試塊的主題;第二個參數是一個函數,包含一組測試用例。

即使沒有顯式定義 describe 函數,每個測試文件也會在最外層預設加上一層 describe 包裹。

在 describe 組成的每個塊中,存在一些鉤子函數,貫穿測試用例的整個過程。這些鉤子函數主要用於測試用例執行之前的準備工作或之後的清理工作。

常用的鉤子函數
  • beforeAll 函數在一個 describe 塊開始之前執行一次
  • afterAll 函數在一個 describe 塊結束之後執行一次
  • beforeEach 函數在每個測試用例之前執行
  • afterEach 在每個測試用例之後執行
示例代碼

下麵的示例代碼展示瞭如何使用這些鉤子函數:

describe("測試是否有值", () => {
  beforeAll(() => {
    console.log("beforeAll");
  });
  afterAll(() => {
    console.log("afterAll");
  });
  beforeEach(() => {
    console.log("beforeEach");
  });
  describe("toBeNull", () => {
    beforeAll(() => {
      console.log("toBeNull beforeAll");
    });
    afterAll(() => {
      console.log("toBeNull afterAll");
    });
    beforeEach(() => {
      console.log("toBeNull beforeEach");
    });
    test("toBeNull", () => {
      expect(null).toBeNull();
    });
  });
});
輸出順序

當運行上述測試用例時,輸出的順序如下:

beforeAll
toBeNull beforeAll
beforeEach
toBeNull beforeEach
toBeNull afterAll
afterAll

通過使用這些鉤子函數,你可以更好地管理測試用例的生命周期,確保每次測試都從一個乾凈的狀態開始,併在測試結束後清理掉產生的副作用。

在這一篇測試指南中,我們介紹了Jest 的背景、如何初始化項目、常用的匹配器語法、鉤子函數。下一篇篇將繼續深入探討 Jest 的高級特性,包括 Mock 函數、非同步請求的處理、Mock 請求的模擬、類的模擬以及定時器的模擬、snapshot 的使用。通過這些技術,我們將能夠更高效地編寫和維護測試用例,尤其是在處理複雜非同步邏輯和外部依賴時。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • title: Nuxt Kit API :路徑解析工具 date: 2024/9/22 updated: 2024/9/22 author: cmdragon excerpt: 摘要:本文介紹了Nuxt Kit中用於解析路徑的API工具,包括resolvePath、resolveAlias、find ...
  • Easy-Blog 是一套集成文章發表、頁面創建、知識庫管理、博客後臺管理等功能於一體的博客系統。Github項目地址:https://github.com/fecommunity/easy-blog, 歡迎Star。 ...
  • title: Nuxt Kit中的 Nitro 處理程式 date: 2024/9/21 updated: 2024/9/21 author: cmdragon excerpt: 摘要:本文詳細介紹了在Nuxt 3框架中使用Nitro伺服器引擎的實踐,包括創建處理程式處理HTTP請求、路由和中間件的 ...
  • title: Nuxt Kit 中的模板處理 date: 2024/9/20 updated: 2024/9/20 author: cmdragon excerpt: 摘要:本文詳細介紹了在Nuxt 3框架中,使用Nuxt Kit進行模板處理的方法,包括理解模板基本概念、使用addTemplate動 ...
  • title: Nuxt Kit 中的插件:創建與使用 date: 2024/9/19 updated: 2024/9/19 author: cmdragon excerpt: 摘要:本文介紹了在 Nuxt 3 框架中使用 Nuxt Kit 創建和管理插件的方法,包括使用addPlugin註冊插件、創 ...
  • ‍ 寫在開頭 點贊 + 收藏 學會 一.webpack和vite的區別 1.構建速度不同 Webpack: Webpack的構建速度相對較慢,尤其在大型項目中,因為它需要分析整個依賴圖,進行多次文件掃描和轉譯。 Vite: Vite以開發模式下的極速構建著稱。它利用ES模塊的特性 ...
  • 前言 在Vue3.5版本中響應式 Props 解構終於正式轉正了,這個功能之前一直是試驗性的。這篇文章來帶你搞清楚,一個String類型的props經過解構後明明應該是一個常量了,為什麼還沒丟失響應式呢?本文中使用的Vue版本為歐陽寫文章時的最新版Vue3.5.5 關註公眾號:【前端歐陽】,給自己一 ...
  • Pintree 是一個開源項目,旨在將瀏覽器書簽導出成導航網站。通過簡單的幾步操作,就可以將書簽轉換成一個美觀且易用的導航頁面。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...