常常聽到別人在說 "Unit Test",它有什麼優點、什麼缺點、哪些好處等等…所以抱著學習的心態,想來踹踹看它,有講錯也可以說喔,感謝!!
這系列會說自己對 Unit test 的理解,作為該系列的第一篇,主要將集中在 UT 概念跟名詞的解釋,會從以下5點來看
- Unit test 在開發中的定位是什麼
- 為什麼需要做 Unit test
- Unit test 會遇到的名詞
- 寫 Unit test code 有哪些 rule
- 執行 Unit test 該注意的點
所以本篇不會有太多 code or XXX 框架怎麼去做UT,實作之後會再寫其他篇來說,接下來我們就馬上開始吧!
Unit test 在開發中的定位是什麼
要定義這點,我們必需從系統開發的角度由上往下看,目前系統開發的方式有很多種, TDD (Test-Driven Development) 測試驅動開發、BDD (Behavior-driven development) 行為驅動開發 等等… 既然說到測試的話,我們借用一下 TDD 的 testing life cycle 圖來看看 Unit test 的位子,如下:
圖中藍色框起來的為 Unit test,它是 TDD testing life cycle 最小的單位,顯而易見的,是拿 code中具有邏輯的最小單位,去進行測試 (通常是 Function or Class),以達到保護系統邏輯不會在持續開發 or 維護的過程中被破壞,並確保 Code 的品質,這就是 UT 的定位。
白話文: 好比做一杯奶茶,我們先確保最基底的紅茶 & 牛奶都 ok ,兩個加在一起就應該是 ok 的奶茶,不會變奶綠。
為什麼需要做 Unit test
系統開發會需要到做 Unit test 有幾個原因,如下:
- 客戶有要求系統測試覆蓋率 (Coverage);至於 Coverage 到底詳細是什麼,後面名詞的部分會講解。
- 商業邏輯較複雜 or 多人參與開發;這類容易產生改 A 錯 B 的狀況,尤其是接手別人的 code,不知道邏輯,時間又有限的話,本來想只是加個 if 判斷,卻造成別邊拿到錯的 data,若又在不同 page 會更難察覺,好點的在內部被測出來,差的就上 PRD 被 User 踩到,所以希望在開發期間就抓到是最好。
- 跨 project 的 component 提供者 or open source library;要做給別人用的,所以就盡量確保 code 的品質囉!
Unit test 會遇到的名詞
要看懂測試結果,就要先搞懂結果上會出現名詞,前面提到的覆蓋率 (Coverage)是個通稱,把它展開細節有5個名詞,對應就是5種 Coverage,下圖紅框內的部分
這邊借用 Jest 這套工具顯示的結果圖來看,至於好奇 Jest 的小夥伴可以先看[這裡],先來說一下這些名詞吧!
- Stmts (Statement):
以一個最小程式語法為單位,就是一段 code 的statement,表示跑測試時其中有多少比例的 statement 被執行到。
2. Branch
以一個測試情境為單位,表示跑過測試的比例有多少,例如: function 中 if else 有2種情境,下例只測試1種,else 的情境並沒測試到,所以2個 branch 只測試到其中1種,就是50%。
3. Funcs (Functions)
以一個 function 為單位,表示 Files 中有多少比例的 function 被執行,以上面的範例來說其中只有一個名為 chnage 的 function,所以單只這隻 file 去測試的話,Funcs 欄位就會是 100%。
4. Lines
以一行為單位,就算是一個 lines,表示跑測試時其中有多少比例的 lines 被執行到,基本上 lines 應該是大於等於 statement 的。
5. Uncoverd Lines
以一行為單位,與上述 lines 相反,表示未被測試到語句的行數。
寫 Unit test code 有哪些 rule
我們這邊會借用 Jest + Enzyme 的寫法來演示,如下:
1.變數 & Functions 命名 & 測試說明必須清晰一致
上例顯示,每個命名中,帶有測試項目 & 預期想達到的結果,都有清楚的寫出來,當後續在看報告時,若有 error 就能馬上知道,哪邊錯;它正確該是什麼,如果命名成: handle check value,你將無法立刻看出來。
2.單一責任;一個測試任務應該只測一個功能
從上例來看,describe 為一個測試任務的 block,它是針對 counter function 做的測試,所以連帶該區塊之下的測試,都該只是這個 function 內的 case,不該有超出 counter function 範圍的測試存在。
3. 一次僅測試一段功能的代碼
從上例來看,describe 中有兩個最小單位的 it 測試 block,測試 counter function 之下的兩個功能,分別是 value 的增跟減,若把兩個混在一起測,會產生 it 命名的問題之外,當先測試了增加 1 後,在跑減少 1 將導致本該為 -1 的 value 變成了 0,就與預期不符了。
4.Test case 應該是獨立的,即便某一需求有任何增強或變化,一般來說其他的單元測試,不應該受到影響
假設我們將上述 Counter 功能增強,多了乘跟除的計算,顯而易見的,應該只要加上乘跟除的測試,並判斷結果是否正確,不該對增跟減有影響。
5. 測試時,請考慮所有執行的場景
假設我們有個 function 它帶有一個流程控制,例如: if & elase,那這時理當兩種 case 都該測過。
執行 Unit test 該注意的點
- 專案實務上,往往只能測 "最小可行性功能"
就開發來說,會有許多不影響主功能運行的 function,例如: 有一個 Todo List 功能,在畫面上的操作有以下幾點
Step-1. 初始化時,call api 拿取 data 後顯示,且每個 item 都有個 checkbox
Step-2. 當某個 checkbox 被選中時,該 item 底色變為 highlight
Step-3. 點 submit 時,再次 call api,送給後端這個被選到的 item data
Step-4. 跳出 submit 成功的提示訊息
要達成上述 Todo List 最基本的功能,是秀出 data 跟 送回 data 給後端, 對應就是 Step-1 & Step-3,我把這個稱為 "最小可行性功能",至於底色變不變、提示跳不跳,都不影響主功能運行,在時間有緊迫的狀況時,可以只對 "最小可行性功能" 作測試即可。
2. 一昧追求覆蓋率 (Coverage),可能是雙面刃
覆蓋率越高會給人一種 code 越少 bug 的感覺,導致初識 Unit test 的人會有種誤解,想追求 80% 甚至更高的安全感,但就實務面如上述所說的,專案進行中會有很多問題來影響做 Unit test 的程度,常見的如下:
- 時間 : 專案的時程可能趕到沒時間給你寫 UT
- 人力 : 一定規模的公司會有專門寫 UT 的工程師,沒有的話,量力而為
- 能力 : 要寫好 UT 會很吃系統架構的能力,且如果是接手舊專案,可能修 bug 跟搞懂邏輯都來不及了,更別提改寫它,解耦再做測試了
所以識情況而定,有時候與其追求覆蓋率超高,不如先思考如何架構做得漂亮、低耦合度,這樣一來只要做好 "最小可行性功能" 的測試,就更省力些也能確保不出現具毀滅性的 bug。
3. 只能測試已知的情境
Unit test 的執行,是建立在已經知道 "對的情況",進而去預想每個情境下,所做的事情是否符合這個 "對的情況",無法測試未預期的情況,例如: 今天有隻 api 的 respond,連自己後端的人都不知道會回什麼格式,那是絕對無法做 Unit test 的,因為怎麼測都不算對。
概念跟名詞說完了,接下來就是實作的部分,一樣會是前端部分的 Unit test,接下來預計會做 React & Angular 兩種框架的測試,後續本系列會在更新出來,感謝!!
UT 系列:
[UT] What’s unit test ? 在前端要測什麼 ?!
[UT] Angular 召喚 Karma + Jasmine 來做個單元測試吧 I
[UT] Angular 召喚 Karma + Jasmine 來做個單元測試吧 II
[UT] Angular 召喚 Karma + Jasmine 來做個單元測試吧 III
Source:
JavaScript data types and data structures