? 令人愉快的 JavaScript 測試

水墨寒發表於2018-10-12

1 簡介

? 令人愉快的 JavaScript 測試

1.1 Jest 是什麼?

Jest 是 Facebook 釋出的一個開源的、基於 Jasmine 框架的 JavaScript 單元測試工具。提供了包括內建的測試環境 DOM API 支援、斷言庫、Mock 庫等,還包含了 Snapshot Testing、 Instant Feedback 等特性。它自動整合了斷言、JSDom、覆蓋率報告等開發者所需要的所有測試工具,是一款幾乎零配置的測試框架。並且它對同樣是 Facebook 的開源前端框架 React 的測試十分友好。

1.2 誰在用?

? 令人愉快的 JavaScript 測試

1.3 賣點

  • Jest 是 Facebook 出品的一個測試框架,相對其他測試框架,其一大特點就是就是內建了常用的測試工具,比如自帶斷言、測試覆蓋率工具,實現了開箱即用。
  • 而作為一個面向前端的測試框架, Jest 可以利用其特有的快照測試功能,通過比對 UI 程式碼生成的快照檔案,實現對 React 等常見框架的自動測試。
  • 此外, Jest 的測試用例是並行執行的,而且只執行發生改變的檔案所對應的測試,提升了測試速度。目前在 Github 上其 star 數已經破萬;而除了 Facebook 外,業內其他公司也開始從其它測試框架轉向 Jest ,比如 Airbnb 的嘗試 ,相信未來 Jest 的發展趨勢仍會比較迅猛。

2 課前準備

2.1 測試框架

測試框架的作用是提供一些方便的語法來描述測試用例,以及對用例進行分組。 測試框架可分為兩種: TDD (測試驅動開發)和 BDD (行為驅動開發),我理解兩者間的區別主要是一些語法上的不同,其中 BDD 提供了提供了可讀性更好的用例語法,至於詳細的區別可參見The Difference Between TDD and BDD - Josh Davis 一文。 常見的測試框架有 Jasmine, Mocha 以及本文要介紹的 Jest 。

2.2 斷言庫

斷言庫主要提供語義化方法,用於對參與測試的值做各種各樣的判斷。這些語義化方法會返回測試的結果,要麼成功、要麼失敗。常見的斷言庫有 Should.js, Chai.js 等。

測試覆蓋率工具

用於統計測試用例對程式碼的測試情況,生成相應的報表,比如 istanbul


3 上手

初始化一個專案

mkidr $CODE_PATH && cd $CODE_PATH
npm init -y
npm i -D jest
複製程式碼

⚠️ jest 預設不支援es6 如果需要測試es6 需要額外安裝
npm i -D babel-jest babel-core babel-preset-env
並且增加.babelrc 配置如下:

{
  "presets": ["env"]
}
複製程式碼

package.json新增測試指令碼

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watchAll"
  }
}
複製程式碼

這樣我們的食材就準備好了✌️

3.1 第一個例子? 1+2 = 3 讓我們從寫一個兩個數相加的示例函式開始。首先,建立 sum.js sum.test.js 檔案︰

touch sum.js sum.test.js
code .
複製程式碼

::sum.js::

export default function sum(a, b) {
  return a + b
}
複製程式碼

::sum.test.js::

import sum from './sum'
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})
複製程式碼

⚠️ Jest 的測試指令碼名形如**.test.js,不論 Jest 是全域性執行還是通過npm test執行,它都會執行當前目錄下所有的**.test.js 或 *.spec.js 檔案、完成測試。
執行npm run test

? 令人愉快的 JavaScript 測試

以上是官網的一個小例子


3.2 新的嘗試

? 你可以修改下 函式sum試試錯誤的結果
::sum.js::

export default function sum(a, b) {
  return a + b + 1
}
複製程式碼

執行npm run test

? 令人愉快的 JavaScript 測試
測試沒通過?,如果我們學會了使用jest 那我們的工作流程將會變成:

需求分析 ➡️ 寫單元測試 ➡️ 寫程式碼 ➡️ 單元測試全部通過
那麼就意味著單元測試全部通過就沒bug了麼?那可不一定

? 令人愉快的 JavaScript 測試
那麼為什麼要寫單元測試?詳見寫單元測試的重要性


4 常用的幾個Jest斷言

接下來寫一個函式的集合 ::functions.js::和::functions.test.js::來演示一些常用的測試方法

touch functions.js functions.test.js
複製程式碼

::functions.js::

export default {
  add: (num1, num2) => num1 + num2,
}
複製程式碼

::functions.test.js::

import functions from './functions'
const { add } = functions
// toBe
test('Adds 2 + 2 to equal 4', () => {
  expect(add(2, 2)).toBe(4)
})
複製程式碼

4.1 not

在::functions.test.js::增加測試

...
// not
test('Adds 2 + 2 to NOT equal 5', () => {
  expect(functions.add(2, 2)).not.toBe(5);
});
...
複製程式碼

.not修飾符允許你測試結果不等於某個值的情況,這和英語的語法幾乎完全一樣,很好理解。

4.2 toBeNull

::functions.js::

export default {
  add: (num1, num2) => num1 + num2,
	isNull: () => null
}
複製程式碼

在::functions.test.js::增加測試

// toBeNull
test('Should be null', () => {
  expect(isNull()).toBeNull();
});
複製程式碼

測試通過

4.3 toBeFalsy

toBeFalsy 判斷值是否為false

// toBeFalsy
test('Should be falsy', () => {
  expect(undefined).toBeFalsy()
})
複製程式碼

4.4 toBeFalsy/toBe

// functions.js
createUser: () => {
  const user = { firstName: 'Brad' };
  user['lastName'] = 'Traversy';
  return user;
}
// functions.test.js
test('User should be Brad Traversy object', () => {
  expect(createUser()).toEqual({
    firstName: 'Brad',
    lastName: 'Traversy'
  });
});
複製程式碼

⚠️ .toEqual匹配器會遞迴的檢查物件所有屬性和屬性值是否相等,所以如果要進行應用型別的比較時,請使用.toEqual匹配器而不是.toBe

4.5 More

jest 使用的斷言風格與社群相差無幾。

// 數值對比
expect(8).toBeGreaterThan(7)
expect(7).toBeGreaterThanOrEqual(7)
expect(6).toBeLessThan(7)
expect(6).toBeLessThanOrEqual(6)

// Regex 正則匹配
expect('team').not.toMatch(/I/i)

// Arrays
expect(['john', 'karen', 'admin']).toContain('admin');
複製程式碼

更多斷言方法參見:Expect · Jest

4.6 測試非同步

JSONPlaceholder是一個提供免費的線上 REST API 的網站,我們在開發時可以使用它提供的 url 地址測試下網路請求以及請求引數。或者當我們程式需要獲取一些假資料、假圖片時也可以使用它。

// functions.js
fetchUser: () =>
	axios
    .get('https://jsonplaceholder.typicode.com/users/1')
    .then(res => res.data)
// functions.test.js
// Async Await
test('User fetched name should be Leanne Graham', async () => {
  expect.assertions(1)
  const data = await functions.fetchUser()
  expect(data.name).toEqual('Leanne Graham')
})
複製程式碼

上面我們呼叫了expect.assertions(1),它能確保在非同步的測試用例中,有一個斷言會在回撥函式中被執行。這在進行非同步程式碼的測試中十分有效。doc.ebichu.cc/jest/docs/z… 具體的文件說明。

5 實戰

5.1 chunkArray

來寫個函式::chunkArray:: 將陣列array拆分成多個 size 長度的區塊,並將這些區塊組成一個新陣列。 如果array 無法被分割成全部等長的區塊,那麼最後剩餘的元素將組成一個區塊。 舉例:

  • 例項1: 陣列[1,2,3,4,5,6,7,8,9,10] 拆分的size 為2 那麼 最後輸出結果為[[1,2],[3,4],[5,6],[7,8],[9,10]]
  • 例項2: 陣列[1,2,3,4,5,6,7]拆分的size 為3 那麼輸出結果為[[1,2,3],[4,5,6],[7] 以上就是分析過程,也就是我們日常開發中的需求描述,現在我們把需求轉換成單測 ::chunkArray.test.js::
// 確認chunkArray 已經被宣告定義
test('chunkArray function exists', () => {
  expect(chunkArray).toBeDefined()
})

// 驗證例項1
test('Chunk an array of 10 values with length of 2', () => {
  const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  const len = 2;
  const chunkedArr = chunkArray(numbers, len);
  expect(chunkedArr).toEqual([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]);
});
// 驗證例項2
test('Chunk an array of 10 values with length of 2', () => {
  const numbers = [1, 2, 3, 4, 5, 6, 7]
  const len = 3
  const chunkedArr = chunkArray(numbers, len)
  expect(chunkedArr).toEqual([[1, 2, 3],[4, 5, 6], [7])
})
複製程式碼

接下來就是寫chunkArray函式了,給出參考 ::chunkArray.js::

const chunkArray = (arr, len) => {
  // Init chunked arr
  const chunkedArr = []

  // Loop through arr
  arr.forEach(val => {
    // Get last element
    const last = chunkedArr[chunkedArr.length - 1]

    // Check if last and if last length is equal to the chunk len
    if (!last || last.length === len) {
      chunkedArr.push([val])
    } else {
      last.push(val)
    }
  })

  return chunkedArr
}
複製程式碼

如上,是開發流程,這個玩法在一些演算法刷演算法的社群也有體現,比如codewars

? 令人愉快的 JavaScript 測試

? 寫到這裡我在思考單元測試的意義在哪?總結如下:

  1. 寫單測的是對需求的梳理,動手寫程式碼之前,讓自己對於需求的理解更加明確。
  2. 單測像一個機器人在幫你值守模組的正確性,隨著專案複雜和規模變大,單測能讓開發者提前知道因為模組的修改和變動的影響範圍。

6 測試覆蓋率

Jest 內建了測試覆蓋率工具istanbul,在::package.json::中增加配置

"collectCoverage": true,
"coverageReporters": ["json", "html", "text"]
複製程式碼

npm run test在命令列中會出測試覆蓋率,

? 令人愉快的 JavaScript 測試
同時在工程目錄下生成coverage目錄有網頁版的報告
? 令人愉快的 JavaScript 測試


X. 參考文獻

  1. 使用Jest測試JavaScript (入門篇)
  2. The Difference Between TDD and BDD - Josh Davis
  3. 前端測試框架 Jest
  4. 無單測、不編碼——寫單元測試的重要性-HollisChuang’s Blog
  5. Jest Crash Course - Unit Testing in JavaScript - YouTube

相關文章