上上個禮拜參加 MOPCON 2019 時,聽到神 Q 超人介紹了『我們與測試的距離』,當時他問了在場的會眾說:『有聽過單元測試的舉手?』現場大概有八成的人都舉手了,接著他再問:『有在前端導入測試的舉手?』結果很意外的,居然幾乎沒有人舉手!
我想這應該是因為目前大多數的前端開發還是都以人工進行測試比較多。隨著專案當規模變大時,在產品上線前都會進行多次人工的測試,或是進行程式碼的重構,這都是常見的現象。但也經常因為重構或是測試後進行調整時出現改 A 壞 B 的情況,又或者花在人工測試的時間太多,因此這時就可以試著導入單元測試來輔助開發過程。
何謂單元測試?
以 function 為最小單位,程式裡每個單一行為,可稱為一個單元。
例如:
- 按下按鈕事件觸發
- API function 執行與回傳
而單元測試則是利用貼近實際使用的腳本來進行對程式碼的檢查與驗證,確保函式的輸入輸出結果是符合需求且可以正確運行的。
單元測試的好處
- 提高正確性:
透過測試可以對於功能輸入輸出有個快速的回饋,開發時也可以驗證函式邏輯與想法。 - 隨時可測試
隨時都可以開啟測試,不一定要等到功能完全寫完後才進行人工的測試,當其中出現問題時,還需要一一驗證是哪個環節出問題。 - 花費時間短
透過自動化重複測試的部分,不需要一再進行耗時的重複人工測試。 - 測試後能產生測試報告(覆蓋率報告)
透過單元測試的工具,可以輸出測試後的測試報告,也包含此次測試所覆蓋的程式碼驗證程度,讓測試不會口說無憑!
兩個常被提到的單元測試面向 - TDD & BDD
TDD(Test-driven development)測試驅動開發
TDD 是在開發前先寫測試的程式碼,由通過測試來推動開發的進行。
TDD 的主要運行圖:
圖片引用自 hybrid-development-with-tdd-ddd-bdd
從圖中可看出 TDD 的運作是『紅燈』(Red)、『綠燈』(Green)、『綠燈』(Refactor) 的循環。
這個循環也可以簡單的分為 6 個步驟:
1. 分析需求、思考實作:
根據需求取設計一個自動化的測試實例。
2. 運行所有測試:
在目前開發的程式碼上運行這些自動化測試。在此階段通常會出現紅燈(因為根本還沒開始開發嘛~~~)
3. 開發符合測試需求的程式碼
以功能邏輯為主進行開發,並使其通過測試案例
4. 再次運行測試,並確認是否通過測試
首先要確保功能的開發是否有正確得到實作,若是通過測試(綠燈)代表大致上已經完成一個可正確運行的版本。
5. 重構
這個步驟是可做可不做,但重構可以提高程式碼品質以及提升複用性。
6. 重複以上步驟
有新的測試(對應每個功能)都可以重複步驟 1-5,直到所以測試都通過。
流程圖參考:
圖片來自 TDD Vs BDD – Analyze The Differences With Examples
使用 TDD 的方式,就好像是腳踏車的輪子,都是向同一個方向轉動(相同專案需求目標),但後輪是施力的讓腳踏車往前,而前輪是受力的,被帶動而轉(測試驅動開發)。
TDD 實例可以參考這篇文章 TDD 開發五步驟 中提到的『TDD 模式示範』。
BDD(Behavior-driven development)行為驅動開發
BDD 的主要目標是以行為為導向去寫測試,透過利益相關者又或者是專案成員(開發人員、測試人員、用戶)描述系統功能和業務邏輯,來了解預期對系統的需求與認知,進而根據這些描述來進行系統自動化的測試。
BDD 流程:
1. 根據 user story 定義出具體的目標:
- Given(上下文,如:環境條件、情境先決條件)
- When(事件、當下的動作)
- Then(預期結果、其他結果)
例如:
Given 有個顧客擁有一個銀行帳戶
When 顧客要從帳戶中轉帳到海外帳戶
Then 這筆帳轉到海外帳戶
And 並從這個帳戶中收取轉帳的小費
2. 將定義出的敘述轉為程式碼
BDD 的過程可以參考這一篇文章 自動軟體測試 中 BDD (Behavior-driven development)
這邊有舉出 BDD 實例。
總結 TDD 與 BDD 差異
BDD 和 TDD 有個各自適用的情境,TDD 在於高效且快速開發並測試功能模組,在開發前先寫測試,以確保程式碼品質與符合需求的結果。BDD 比較偏向於系統功能與業務邏輯的自動化測試,除了實作前先寫測試外,通常還會產出一分執行規格。因為強調的是系統行為,因此也會減少使用者和工程師的溝通成本。
測試工具入門初體驗
以上講了這麼多觀念,當然要搭配程式碼來理解一下!這邊使用 Jest 來初步做一個簡單的測試。
在專案中載入 Jest
1
npm install jest
在專案中加入 sum.js
寫入一個加總的函式,讓函式返回結果為 a+b1
2
3
4
5
6
7
8
9let math = {
sum: function(a, b) {
return a + b;
},
minus: function(a, b) {
return a - b;
}
}
module.exports = math;在專案中加入 sum.test.js
test 的 js 中需要將要測試的函式 require 進來,接著寫下要測試的內容,並寫在預期測試的結果。1
2
3
4
5
6
7
8
9const math = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(math.sum(1, 2)).toBe(3);
});
test('minus 4 - 2 to equal 2', () => {
expect(math.minus(4, 2)).toBe(2);
});在 package.json 加入 test
1
2
3"scripts": {
"test": "jest"
},運行 npm run test 看結果
如果 test 成功會出現如下圖
我們試著將函式修改一下(假裝不小心把 return 結果寫相反),例如:1
2
3
4
5
6sum: function(a, b) {
return a - b;
},
minus: function(a, b) {
return a + b;
}
那運行測試的結果一定是錯誤的,接著就會看到如下圖:
本文小結
這篇主要著重於了解單元測試的基礎觀念,接下來持續往實作的方向做介紹。
初步開始了解單元測試,若是文章中的解釋有誤,再請多多指教 :D
參考文獻
認識軟體測試的世界 & TDD/BDD 入門
前端單元測試Unit Test
浅谈前端单元测试
單元測試:Mocha、Chai 和 Sinon