/Unit Testing, JavaScript, Jest

Jest | 讓 Jest 為你的 Code 做測試-基礎用法教學

前言

單元測試是進入前端工程後一直很想學的技能,主要是做過測試能讓自己的程式碼維持一定的水準,尤其在團隊開發時,更不會在上線時因為沒注意到某個細節而產生 Bug ,導致專案出現問題。


單元測試

單元測試是指為專案中每個單一行為做測試,通常專案裡的最小單位都是一個 ,當每個 經過測試,確保邏輯是正確的,那當他在專案裡運行時也就比較不會發生問題,且留下的測試文件也可以在團隊討論或交接時更清楚。

Jest

用於前端的測試框架不是只有 ,選擇原因是因為筆者較擅長使用 ,而 在測試方面和 的整合度較佳。除了 以外,常聽到的測試框架還有

建立專案

首先到專案資料夾的目錄下,在 環境下創建 專案:


這時候在專案資料夾內會產生記錄著專案開發設定的 ,如果還不曉得如何安裝 可以參考「第一次建置node.js開發環境和安裝npm就上手!」的說明。

下載套件

透過 套件管理工具下載 測試框架到專案的執行環境中:


設定測試指令

打開 ,可以看到剛剛下載的 已經被記錄在 中了:

下載在開發環境的 Jest 會被記錄在 package.json 中

在上圖第一行的 是在 的開發環境中設定執行指令的地方,上方的 便是預設的指令之一,可以直接把他的內容改掉,如下:


設定好後,只需在終端機中使用以下方式輸入,便能執行設定好的對應指令:


需要注意的是,如果是使用 執行指令,那他的執行環境就是專案本身,尋找執行環境中的 或其他套件執行。

換個說法,直接在終端機中輸入 ,執行環境就不會在專案內,而是以全域為主,但是全域環境下並沒有安裝 便會出錯,除非將 安裝在全域中:


建立測試

在執行測試時,會尋找專案中副檔名為 結尾的檔案,但不限制要放在哪個資料夾,所以在根目錄新增一個 建立第一個測試:

test('Check the result of 5 + ', () => {
    expect(5 + 2).toBe(7)
})

把上方的 當作一個函式,負責描寫一個單元測試,他擁有兩個參數:

  1. 第一個參數為「測試名稱」,能夠簡單描述這部分是在測試什麼邏輯或功能。
  2. 第二個參數是一個函式,又稱斷言,函式內的 用來描述被測試的內容, 是測試內容的回傳值是否符合期望值,例如上方的測試內容為「5加上2期望會等於7」。

執行測試

在終端機中輸入在 中設定好的指令:


執行後會顯示測試的結果:

通過 Jest 測試

結果內會顯示 測試了哪些 檔案,還有每個測試( )內的結果( )是否正確符合,符合的話會輸出 PASS。

現在把 內的數字改成 8 ,再進行一次測試:

未通過 Jset 測試

當測試失敗時, 會再結果中顯示哪個檔案內的測試有問題,並會提示正確的結果

這就是使用 測試的基本方法,但實際上需要測試的函式都已經寫好了,並不會在 中寫下邏輯,因此可以直接在 中呼叫函式,測試結果的正確性,例如:

//要測試的函式
const sum = (a, b) => {
    return a + b
}

test('Check the result of 5 + 2', () => {
    //在 expect 中呼叫函式測試結果
    expect(sum(5, 2)).toBe(7)
})

其他斷言

斷言的種類有很多,上方的 只是其中一種測試方式,除此之外還有以下的斷言可以使用:

除了 外,對字串還可以用 搭配正規表達式檢查:

test('Use toMatch test',()=>{
    //搭配正規表達式
    expect('Happy new year.').toMatch(/new/)
})

確認物件是否等於期望值需使用

test('Check the object type', () => {
    let peopleA = {
        name: 'GQSM'
    }
    peopleA.age = 25

    //測試字串
    expect(peopleA.name).toBe('GQSM')
    //測試物件
    expect(peopleA).toEqual({ name: 'GQSM', age: 25 })
})

預防函式回傳某個結果可以使用

test('Use not', () => {
    let peopleA = {
        name: 'GQSM'
    }
    //確認 name 不等於空
    expect(peopleA.name).not.toBe('')
    peopleA.name = ''
    //如果 name 是空的
    expect(peopleA.name).not.toBe('')
})

上方的第 9 行 是空的,而在使用 的情況下結果需不等於期望值,所以不會通過測試:

使用 not 測試代表結果需不等於期望值

確認數字結果的斷言:

test('test integer', () => {
    //整數可以使用 toBe 或 toEqual 斷言
    expect(5).toBe(5)
    expect(5).toEqual(5)

    //測試輸出值是否大於期望值
    expect(5).toBeGreaterThan(4)

    //測試輸出值是否大於等於期望值
    expect(5).toBeGreaterThanOrEqual(5)

    //測試輸出值是否小於期望值
    expect(5).toBeLessThan(6)

    //測試輸出值是否小於期望值
    expect(5).toBeLessThanOrEqual(5)
})

需要注意在 中的小數點運算會產生誤差,因此浮點數需要使用 做斷言,他會捨棄掉些微的誤差:

test('Test float', () => {
    //會忽略些微的誤差
    expect(0.1 + 0.2).toBeCloseTo(0.3)
    //需完全相等
    expect(0.1 + 0.2).toBe(0.3)
})

上方使用 會不通過測試,因為 需要完全符合:

JavaScript 的浮點數會出現誤差

對陣列可以用 判斷陣列內是否含有某值,或搭配迴圈對每個位置做斷言:

test('For array test in jest',()=>{
    let arrA = ['A','B','C']

    //檢查陣列內是否含有某值
    expect(arrA).toContain('B')
    
    //搭配迴圈檢查每個位置都不等於空
    for(let i in arrA){
        expect(arrA[i]).not.toBe('')
    }
})

最後是用來判斷特殊值的斷言,例如 等等:

test('Special value',()=>{
    //期望值為 true
    expect(true).toBeTruthy()
    
    //期望值為 false
    expect(false).toBeFalsy()

    //期望值為 null
    expect(null).toBeNull()

    //期望值為 undefined
    expect(undefined).toBeUndefined()

    //期望值為 undefined 之外的值
    expect(null).toBeDefined()
})

產生覆蓋率報告

在測試的時候,會使用各種斷言來確認結果是否與期望值符合,但是如果測試的內容遺漏了某個條件分支,便無法確認該分支的邏輯性是否正確。

覆蓋率就是用來統計被測試的函式,程式碼的執行比例,當函式內所有程式都被測試過,那覆蓋率就會呈現 100% 。

另外, 內建的覆蓋率會以每個 檔案統計,因此在產生前還需要將「函式」與「測試」檔案分開,如下:

建立 放要測試的函式:

const sum = (a, b) => {
    if (b)
        return a + b
    else
        return a
}

module.exports = {
    sum: sum
}

中的 有兩個分支,一個是在 有值的時候回傳 ,另一個是在 沒有值的情況下直接回傳 ,最後使用 匯出。

接著建立測試檔案

let func = require('../funcs/func.js')

test('test sum',()=>{
    expect(func.sum(2)).toBe(2)
})

最後到 中在 的指令後加上 ,讓 執行完時同步產生測試報告,當然也可以另外設定新指令:

在 jest 後方加上 — coverage

執行測試後,除了測試結果外,還會產生覆蓋率統計資訊:

覆蓋率的統計資訊,由左至右分別為:語法、分支、函式數、行數

也可以發現在專案目錄下多了一個叫做 的資料夾,裡面的 內有個 ,打開後也可以看到相同的資訊:

./converage/Icov-report/index.html

透過點擊 可以確認更詳細的測試過程,包含是哪一行沒有執行,或每行各執行了幾次:

紅色區塊代表未執行,也就是未覆蓋的地方,行數上的 1x 代表測試中執行次數

現在為 內的 增加一個斷言句,讓 在測試時進入 的另一個分支:

let func = require('../funcs/func.js')

test('test sum', () => {
    expect(func.sum(2)).toBe(2)
    //增加斷言
    expect(func.sum(2, 3)).toBe(5)
})

執行測試後再一次點開 內的 測試報告:

覆蓋率已達 100%

的詳細執行資訊,在判斷 是否有值的地方執行了兩次,且已經沒有還未覆蓋的區塊:

已經沒有未覆蓋的區塊了


本文解釋了基本的 測試,並簡單描述斷言庫和產生覆蓋率報告,今後的文章會繼續解釋 提供的其他功能,一直到用於測試 組件。

如果文章中有任何問題,或是不理解的地方,都歡迎留言告訴我,謝謝大家!

參考文章

  1. https://jestjs.io/docs/en/getting-started
  2. http://blog.404mzk.com/jest.html
  3. https://zhuanlan.zhihu.com/p/28247899