寫這篇緣故
最近在幫公司講解關於單元測試的技術,擷取部分在91課程上寫測試要點還有自己寫測試的經驗,做一個整理,當作是幫公司培訓工程師之類
不過我建議還是給外面的老師指點和上一下會比較好,因為會比較清晰!
單元測試常見的名詞
- SUT:System Under Test/Software Under Test 【待測程式】,在Unit就是待測試的項目,EX:我們要測的是類別、物件還是方法,都可以統稱 SUT。
- DOC:Depended-on Component【相依元件】,例如:成立訂單函數,如果訂單失敗異常會寫入log,log函數就是成立訂單函數的DOC。
在實務上筆者在寫單元測試的時候,因為SUT會去呼叫其他的物件也會呈現出SUT依賴DOC, 在測試SUT勢必會有DOC存在,搞得測試很複雜。
單元測試的3A
- Arrange – 初始化-白話一點就是準備要測的資料,更詳細一點說穿就是在準備演員和劇本
- Act – 執行測試的目標,並取得實際結果
- Assert – 驗證結果
[TestClass]
public class CalculatorTest
{
[TestMethod]
public void Calculator_TenAddTwenty_ReturnThirty()
{
//Arrange
var calc = new Calculator();
int x = 10, y = 20;
//Act
int actual = calc.Add(x, y);
//Assert
int expected = 30;
Assert.AreEqual(expected, actual);
}
}
public class Calculator
{
public int Add(int x, int y)
{
return x + y;
}
}
為何要寫單元測試
不知你可曾遇過這樣的狀況….
情境一: 當你接手時候一個舊系統,經過團隊交接輪替狀況下,業主或是客戶要你改一個功能,你卻不知道從哪下手呢?
單元測試能幫你做什麼?
單元測試他就像活者的情境測試的規格書,就像是API的input和output,我要預期得到什麼樣結果來進行做驗證,他就像是一個情境的案例。
情境二: 是否曾經改發生過改好新功能,但舊功能就壞掉,修好舊功能,新功能又壞掉了呢?
單元測試能幫你做什麼?
它能告訴你目前的程式,是否執行都符合當時所要求的規格與產出結果,如果舊規格舊方法沒有改的情況下,單元測試壞了,那就表示你這次的改動絕對有影響到它,趕緊去修好它吧!!
情境三 : 你在計算關鍵的金額的時候,偵錯下去才發現,天殺的,原來是自己觀念上錯誤或不熟悉。例如Double a = 1.1加上Double b = 1.2,而你以為它的結果會是2.3….
單元測試能幫你做什麼?
它能即時的驗證你的想法,而不是到上線時才賭人品,尤其是那些你覺得理所當然會對的功能中,魔鬼往往藏在細節裡
在你還沒寫單元測試之前,上述狀況是否都似曾相識呢?如果你想解決這些問題,那麼開始寫單元測試將是可以大幅度降低這些錯誤的有效方法。
Unit Test 神兵利器 — 擷取與覆寫 (Extract and override) 在原類別
- 找出相依物件。
- 開始進行提取方法(Extract Method)。
- 將該private改成protected的方法。
在測試類別
- 繼承原類別,命名為FakeXXXX。
- 覆寫FakeXXXXX中的protected方法。
- 針對protected方法中的物件,建立其setter
- 在測試中,新建FakeXXXX的實例,然後根據情境修改不同的值。
- 測試。
Unit Test 神兵利器 — 相依注入(Dependency Injection)
- 針對外部相依物件,建立其介面(Interface)
- 將新增的介面,加上相依的方法
- 將原本使用相依物件地方,更改為使用其介面。
什麼是屬於不好的單元測試
public class UserService
{
public UserResponseViewModel Login(string id, string pwd)
{
var userResponseViewModel = new UserResponseViewModel();
UserRepository userRepository = new UserRepository();
RoleRepository roleRepository = new RoleRepository();
var user = userRepository.GetUser(id, pwd);
if (user != null)
{
var roleName = roleRepository.GetRoleName(user.RoleId);
if (!string.IsNullOrEmpty(roleName))
{
userResponseViewModel.IsSuccess = true;
userResponseViewModel.Name = user.Name;
userResponseViewModel.RoleName = roleName;
return userResponseViewModel;
}
}
return userResponseViewModel;
}
}
測試程式如下:
[Test]
public void LoginSuccess()
{
var expected = new userResponseViewModel
{
IsSuccess = true,
Name = "老大",
RoleName = "ADMIN"
};
var userService = new UserService();
var result = userService.Login("ADMIN", "ADMIN");
expected.Should().BeEquivalentTo(result);
}
上述這樣狀況會有
- 測試DB改資料怎麼辦?
- DB TimeOut怎麼辦?
- DB沒有資料阿…
- 網路沒開…
- 跑得這麼慢
這測試有哪些狀況
- 不穩
- 慢
- 不方便
沒人想跑,會認為寫單元測試不好,沒效果,浪費時間。
外部物件:DB、API、時間…等
所以問題出在不可控,所以我們要讓它變成可控,那就是要變成假物件。
筆者的產品迭代方式
不可測試的爛code→可測試的爛code→可測試的好code
大部分都是遇到爛Code頂多是進化到寫可測試的濫Code不要壞掉,然後再進一步優化改成好的測試Code跟情境有關測試。
筆者常用的測試替身技巧
- Stub
- Mock
以Stub例子來說:我們要測試登入用的程式是不是有正常運作。塞了一個假的httpClinet物件給他,只要對httpClinet.Post總是回傳 200 OK。這個 httpClinet物件就是Stub。
Mock:Mock注重的是對於行為的測試。
例如:你要測試寫log行為,就可以測試SUT中的Log來呼叫WriteLog()。
單元測試逃脫不了以下該具備的技能
- 物件導向、EX:SOLID、OOP
- 重構
如何在組織或既有工作上進行單元測試的第一步?
- 先從bug和小需求慢慢追加,沒有改到地方,不要隨意重構和調整
- 新的專案開始進行加入測試專案
- 從主分支在切一個分支出來,進行交流和試試水溫
筆者會用到的測試框架
- Nunit 3、NUnit3TestAdapter-單元測試框架
- NSubstitute-測試替身框架
- FluentAssertions-專門用來集合比對集合用、讓測試程式更能口語化
自動測試 VS 手動測試
項目 | 自動測試 | 手動測試 |
---|---|---|
開發時程 | 久 | 短 |
測試時程 | 極快 (秒/分) | 系統越大越慢 (分/時) |
測試完整度 | 依照覆蓋率判斷 | 落觀音 |
系統更新掌握度 | 高 | 有把握自我意識良好, 沒把握時自己嚇自己 |
持久性 | 無限 | 人類會心累 |
CI/CD | 可自動化 | 人工測試/人工佈署 |
改A壞B | 及時反應 | 不明不白 |
假如現在有一陀爛CODE,要增加新功能在那堆爛CODE裡,先把爛CODE抽成method,然後再抽成新的Class針對爛CODE的public的情境先寫測試,會讓需要寫測試的範圍變小很多。
那什麼東西的投資報酬率最高?
- 要修正的BUG(BUG越晚發現修正成本就更高)
- 實務上常跑到的scenario
- 最主要的情境
- 和錢有關的
- 和人命有關的(EX:自動駕駛系統)
- 最常改到的CODE,可以避免被改錯
測試的品質
- 測試的程式一定要重構
- 測試的語意一定要清楚明白,這是為了讓測試更容易理解,可以很簡單的的從測試的程式碼由語意就能理解這個測試要做什麼
- 寫測試的難度會反應程式的好壞
最後幾個常見的問題
1.private要不要寫測試?
A:筆者不會寫這一部分測試,原因是private的上層public呼叫的時候,其實public上層有寫測試的話,private就沒必要測了。
2.關於Static method要不要寫測試?
A:通常這部分Static的函數,我就會看狀況採用DI方式,進行寫測試,筆者很少再寫Static Method。
3.入門單元測測試的話,如何進入這個部分?
A:了解如何【隔離相依】、Stub與Mock的差別,如何寫優秀的單元測試。
補充:測試不應該包含if else for while這些程式邏輯…
引用資料:
測試中常見的名詞:Stub, Dummy, Mock..等等
一起設計出可被單元測試的程式碼
91-課程部分內容-針對遺留代碼加入單元測試的藝術
書籍方向
- 單元測試的藝術第二版