前端測試 (1) - Unit test, Mocha & Chai
工作以來一直需要對新開發的功能或 React 元件在上版前做測試,以保證所有組件與函式能如預期的運作。雖然感覺還沒完全摸透這方面的知識,但仍想紀錄一下自己到目前為止的理解以供日後參考。
Unit Test 單元測試
單元測試就是在程式專案 (project) 中對小單位的程式碼進行測試。聽起來好像很抽象,因為所謂的小單位程式碼跟測試項目是需要自行定義的。以現任公司的作法為例,測試的標的通常是函式、React 元件或整個頁面;而測試進行的項目,除了常見的輸入輸出正確性測試外,也可以測元件有沒有被正確渲染、特定事件有沒有被觸發、特定的變數值是否合乎預期等等。
好處
寫單元測試是費時的,除了要想元件在哪些特定情境可能會有問題,也需要花時間做能製造那些情況的測試資料。雖然麻煩,但卻能帶來以下好處:
- 能自動化進行測試且產生量化的測試數據,並且能在短時間內完成
- 避免在更動元件時改壞原本能正常運行的功能
- 能逐步累積測試案例,盡可能確保元件能在所有情境下正常運作
什麼時候寫
其實也是取決於工程團隊的需求。以現任公司為例,我們通常在開發新的 React 元件或新增 function 到函式庫的時候,就會需要寫相對應的單元測式,例如:測試元件在 mount 的時候有沒有依照 props 或 state 的值正確呈現,或測試切換按鈕被點擊的時候,存在 state 裡相對應的數值有沒有改變等等。
工具
目前公司是用 Mocha, Chai 和 Sinon,安裝方式請參考官方網站,以下將逐一介紹他們的功能。
Mocha 測試框架
用來管理測試案例及定義如何進行測試,在每次啟動單元測試的時候都會被執行。
常用語法
describe()
: 描述當前的測試標的,例如某某 function 或某某元件it()
: 包在describe()
底下,描述要進行的測試案例,通常是元件的預期動作,例如:”should disappear when close button is clicked”before()
: 在跑第一個測試案例前進行的動作after()
: 在最後一個測試案例跑完後要執行的動作beforeEach()
: 在跑每個測試案例前都要進行的動作afterEach()
: 在完成每個測試案例後都要執行的動作
1 | // name of a component/function to be tested |
Chai 斷言庫
在先前的每個 it()
測試案例中,如果元件的行為不合預期,我們以 throw error 的方式告訴 Mocha 該元件沒有通過該案例。這樣雖然直觀好寫,但如果一個案例中有好幾個要檢查的地方就會開始變得複雜(要寫一堆 if-else cases 還有 throw errors),此外要產生的錯誤訊息也會變得難以整理與判斷。
斷言庫就是用來使這些事變容易的工具,除了驗證執行結果是否符合預期,其本身的寫法也能在這些錯誤訊息產生時自動予以分門別類,進而方便管理與判讀。這邊介紹的 Chai 就是其中一套斷言庫,可搭配 Mocha 使用。其他的還有像是:expect.js
, should.js
, unexpected
等等。
Chai 斷言庫提供三種斷言風格,分別是 assert
, expect
, should
。個人覺得他們之間大同小異,選 1~2 種用即可。
1. assert
- 通常寫法為:
assert(expression, error_message)
- 也可寫成:
assert.<compare_function>(compare_target, correct_value, [error_message])
1 | assert('foo' !== 'bar', 'foo is not bar'); |
2. expect
expect()
是一個函式,以測試標的為參數- 寫法貼近自然語言,可以串連多個斷言
- 串連的斷言在預期結果不符時,可以自動生成錯誤訊息
- 也可以另外自訂錯誤訊息
1 | // expect(object).... |
3. should
- 與
expect
類似,可以串連多個斷言並自動生成錯誤訊息 - 不一樣的是
should
是透過 Object.prototype 擴展而來,自動變成每個測試物件裡的屬性 - 無法自訂錯誤訊息,且 IE 瀏覽器不支援這種寫法
- 特定情況下需改變寫法,例如檢查物件是否存在
1 | // object.should.... |
小結
單元測試的測試標的、項目案例與斷言庫的選擇,很大程度取決於團隊的專案需求,並沒有一定的答案。這篇就先記錄到這邊,下一篇來聊聊 Sinon。