一、Jest 和 Mocha 對比選型
至於:前端為什么做單元測試?JavaScript 單元測試的現狀,常見單元測試工具:Jest 和 Mocha 的對比,可以看這篇文章了解:https://mp.weixin.qq.com/s/r08NghhRybAwBX9dzcoE1w
從 github starts & issues 以及 npm 下載量角度來看,Jest 的關注度更高、社區更活躍
框架 | 斷言 | 異步 | 代碼覆蓋率 |
---|---|---|---|
Mocha | 不支持(需要其他庫支持) | 友好 | 不支持(需要其他庫支持) |
Jest | 默認支持 | 友好 | 支持 |
1. Mocha 生態好,但是需要較多的配置來實現高擴展性
2. Jest 開箱即用
無論是受歡迎度和寫法上,Jest 都有很大的優勢,因此推薦你使用開箱即用的 Jest
二、如何使用 Jest 進行單元測試
1、關於安裝依賴的理解:
npm install --save-dev jest
(1)不支持部分 ES6 語法:
原因:nodejs 采用的是 CommonJS 的模塊化規范,使用 require 引入模塊;而 import 是 ES6 的模塊化規范關鍵字。想要使用 import,必須引入 babel 轉義支持,通過 babel 進行編譯,使其變成 node 的模塊化代碼
解決:為了能使用這些新特性,我們就需要使用 babel 把 ES6 轉成 ES5 語法
// 安裝依賴
npm install --save-dev @babel/core @babel/preset-env // 根目錄加入.babelrc
{ "presets": ["@babel/preset-env"] }
原理:jest 運行時內部先執行( jest-babel ),檢測是否安裝 babel-core,然后取 .babelrc 中的配置運行測試之前結合 babel 先把測試用例代碼轉換一遍然后再進行測試
(2)測試 ts 文件:jest 需要借助 .babelrc 去解析 TypeScript 文件再進行測試
// 安裝依賴
npm install --save-dev @babel/preset-typescript // 改寫 .babelrc
{ "presets": ["@babel/preset-env", "@babel/preset-typescript"] } // 為了解決編輯器對 jest 斷言方法的類型報錯,如 test、expect 的報錯,你還需要安裝
npm install --save-dev @types/jest
2、總結一下:
(1)安裝單元測試必要軟件包
npm install -D babel jest @vue/test-utils @babel/preset-env @babel/preset-typescript @types/jest
(2)編寫 babel.config.js
文件
module.exports = { presets: [ ["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript" ] }
3、根目錄創建tests文件夾:ts 測試文件指定后綴為 .test.ts
或 .spec.ts
4、創建 HelloWorld.test.ts 測試文件:跟着 vue3 的官方單元測試get starting創建hello world測試文件
/** * @jest-environment jsdom */
// 上方注釋是指定測試環境,否則報錯document is not define
import { mount } from '@vue/test-utils'
const MessageComponent = { template: '<p>{{ msg }}</p>', props: ['msg'] } test('displays message', () => { const wrapper = mount(MessageComponent, { props: { msg: 'Hello world' } }) // Assert the rendered text of the component
expect(wrapper.text()).toContain('Hello world') })
5、在 package.json
文件中添加 script
{ "script":{ "test": "jest" } }
6、終端運行:npm run test
7、測試實例:
// /src/utils/sum.ts
export function sum(a: number, b: number) { return a + b } // tests/sum.test.ts
import { sum } from '../src/utils/sum' test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3) }) test('adds "2" + 2 to equal 22', () => { expect(sum('2', 2)).toBe('22') })
可以看到 test 均成功。(下面那個 '2' ts 會給提示,但是不影響結果成功,起初我以為會錯誤的,原來不是)
那我們試着給個錯誤試下:它會提示你哪里出錯了
三、更多使用
1、持續監聽:熱更新
為了提高效率,可以通過加啟動參數的方式讓 jest 持續監聽文件的修改,而不需要每次修改完再重新執行測試用例
// 在 package.json 添加
"scripts": { "test": "jest --watchAll" },
這樣的話,如果測試出現錯誤,你改了之后,就會自動支持單元測試,而不需要在輸入命令 npm run test 進行測試,類似於一個熱更新的功能。
2、生成測試覆蓋率報告
什么是單元測試覆蓋率:單元測試覆蓋率是一種軟件測試的度量指標,指在所有功能代碼中,完成了單元測試的代碼所占的比例。有很多自動化測試框架工具可以提供這一統計數據,其中最基礎的計算方式為:單元測試覆蓋率 = 被測代碼行數 / 參測代碼總行數 * 100%
如何生成呢?加入 jest.config.js 文件
module.exports = { collectCoverage: true, // 是否顯示覆蓋率報告 // 告訴 jest 哪些文件需要經過單元測試測試
collectCoverageFrom: ['/tests/helloWorld.test.ts', 'src/utils/sum.ts'], }
再次測試就會生成:
參數名 | 含義 | 說明 |
---|---|---|
% stmts | 語句覆蓋率 | 是不是每個語句都執行了? |
% Branch | 分支覆蓋率 | 是不是每個 if 代碼塊都執行了? |
% Funcs | 函數覆蓋率 | 是不是每個函數都調用了? |
% Lines | 行覆蓋率 | 是不是每一行都執行了? |
3、設置單元測試覆蓋率閥值
個人認為既然在項目中集成了單元測試,那么非常有必要關注單元測試的質量,而覆蓋率則一定程度上客觀的反映了單測的質量,同時我們還可以通過設置單元測試閥值的方式提示用戶是否達到了預期質量。
// jest.config.js 文件加入 // 修改配置
module.exports = { collectCoverage: true, // 是否顯示覆蓋率報告 // 告訴 jest 哪些文件需要經過單元測試測試
collectCoverageFrom: ['/src/views/HelloWorld.vue', 'src/utils/sum.ts'], coverageThreshold: { global: { statements: 90, // 保證每個語句都執行了
functions: 90, // 保證每個函數都調用了
branches: 90, // 保證每個 if 等分支代碼都執行了
}, }, };
上述閥值要求我們的測試用例足夠充分,如果我們的用例沒有足夠充分,則下面的報錯將會幫助你去完善
export function sum(a: number, b: number) { return a + b } export function add(a: number, b: number) { return a - b } export function multi(a: number, b: number) { return a * b }
我又加了 2 個函數,但是單元測試,沒加,所以可以看到覆蓋率是 33.33%,同時可以看到未覆蓋的行數是 5 - 8 行
在 sum.test.ts 中加上另外這 2 個函數,再測試,就可以是 100% 了
import { sum, add, multi } from '../src/utils/sum' test('adds "2" + 2 to equal 22', () => { expect(add(4, 2)).toBe(2) }) test('adds "2" + 2 to equal 22', () => { expect(multi(3, 2)).toBe(6) })
4、常用斷言方法
斷言方法有很多,可以去 Jest 官網 API (https://www.jestjs.cn/docs/expect) 部分查看
// .not 修飾符允許你測試結果不等於某個值的情況
expect(sum(2, 4)).not.toBe(5); // .toEqual 匹配器會遞歸的檢查對象所有屬性和屬性值是否相等,常用來檢測引用類型
expect(getUserInfo()).toEqual(getUserInfo()); //.toHaveLength 可以很方便的用來測試字符串和數組類型的長度是否滿足預期
expect(getIntArray(3)).toHaveLength(3); // .toMatch 傳入一個正則表達式,它允許我們來進行字符串類型的正則匹配
expect(getUserInfo().name).toMatch(/mo/i); // .toContain 匹配對象中是否包含
expect(names).toContain('jim');
5、如何測試異步函數
// 獲取用戶信息 ./servers/fetchUser.js
export const fetchUser = () => { return new Promise((resole) => { setTimeout(() => { resole({ name: 'moji', age: 24, }) }, 2000) }) } // ./test/fetchUser.test.js
import { fetchUser } from '../src/fetchUser'; test('fetchUser() 可以請求到一個用戶名字為 moji', async () => { const data = await fetchUser(); expect(data.name).toBe('moji') })
這里你可能看到這樣一條報錯
這是因為 @babel/preset-nev 不支持 async await 導致的。這時候就需要對 babel 配置進行增強, 可以安裝這個插件解決
npm install --save-dev @babel/plugin-transform-runtime // 同時改寫 .babelrc
{ "presets": ["@babel/preset-env", "@babel/preset-typescript"], "plugins": ["@babel/plugin-transform-runtime"] }
再次運行就不會出現報錯了。