應該知道的事
- 使用 C# , 但是其他語言也適用
- 使用 Visual Studio
- 案例一有基本數理的專有名詞
- 上界(Upper Bound)、下界(lower Bound)、左邊界(Left Bound)、右邊界(Right Bound)
- 報名資訊(已結束)
Agile Meetup 2017/04 意外版: 單元測試這樣玩就對了
案例一、數值區間
1 | 假定給任一整數區間 |
解析
如上範例所示,
「(」「)」小括號(parentheses)表示OPEN
(不包含,大於或小於)
「[」「]」中括號(square brackets)表示CLOSE
(包含,大於等於或小於等於)
(1,6] , 代表這個區間大於 1 小於等於 6,包含的整數有 2、3、4、5、6
[-2,4), 代表這個區間大於等於-2 小於 4,包含的整數有-2、-1、0、1、2、3
這題比較單純,只需要考慮所有的情況,
並且寫成單元測試即可。
- x 落在區間內
- x 落在左邊界外
- x 落在右邊界外
- x 落在左邊界上,左邊界為
OPEN
- x 落在左邊界上,左邊界為
CLOSE
- x 落在右邊界上,右邊界為
OPEN
- x 落在右邊界上,右邊界為
CLOSE
有幾種特殊的情境,特別說明一下
- 假設區間為(0,1),這個區間是不包含任何整數
- 假設區間為(1,1),這個區間是不包含任何整數,且不包含任何數值
- 假設區間為[1,1],這個區間恰巧包含 1 個整數,且只包含 1 這個整數
- 假設”區間”為[2,1],或任何左邊界大右邊界的表示,這不是一個正確的區間,將要作例外處理。
讓我們回歸單元測試,
這裡的重點是一個測試只作一件事,
只把一個情境釐清,並且在測試的程式碼中
明確的表達測試目的
1 | private int leftBound = 1; |
案例二、現在時間轉字串
1 | 寫一個方法GetNowString,不傳入任何參數, |
版本 1
最簡單的寫法:
1 | public class DateHelper |
撰寫測試
1 | [ ] |
解析 1
GetNowString
與系統的時間DateTime.Now
,
是具有耦合性,要解耦需要透過一些 IoC 的手段去處理。
版本 2
利用繼承的方法,作出假的類別
1 | public class DateHelper |
撰寫測試
1 | [ ] |
解析 2
基本上這樣就可以測試了,
原來的代碼,經過一定的重構,
透過virtual
方法 GetNow,
將Datetime.Now
作了隔離
適當利用假類別,取代掉 GetNow 的方法。
這樣夠好了,但是我們可以看看另一種作法
版本 3
先看看我們的DateHelper
,
在這裡我們將 GetNow 交由 IDateProvider 的類別去實作,
如此一來就斷開了耦合性。
1 | public class DateHelper |
實作 IDateProvider 的類別,
在這裡其實不重要.
1 | public class DateProviderV1 : IDateProvider |
讓我們看看測試,
在這裡我們透過一個假的IDateProvider
的實作DateProviderStub
,
完成了測試,
IDateProvider 將DateTime.Now
作了隔離,
並且提供更容易修改的假物件(僅僅需要實作觀注的方法即可,不用擔心繼承帶來的附作用)
1 | [ ] |
1 | public class DateProviderStub : IDateProvider |
圖例解析
我們剛剛究竟幹了什麼?
看看原本的情況,本來的方法因為相依與Datetime
而無法測試
讓我們開始下刀,
先用一個新的方法GetNow
將它與待測的方法作分割,
但是對整個類來說仍舊是耦合。
繼續把這刀往下切,
我們墊一層介面,
待測方法不再直接呼叫GetNow
而是透過介面執行,當然會有額外實作介面與注入的功(請參考程式碼,不在此處繪出了.)
最後,別忘了我們的目的
測試原本的待測方法,
我們可以透過一個假的
類,
來操控他的行為(ex:凍結時間).
如此一來,就可進行測試了.
另外,這種被待測方法呼叫後
會回傳一個假值的方法或類
被叫作STUB
案例三、發送郵件
事先聲明,這題沒有程式碼,
有興趣實作的人可以試試看.
如果可以分享實作後的資訊給我更好 XD
Q:註冊發送郵件如何寫單元測試?
解析
很明顯的發送郵件需要依賴外部的郵件系統,
這裡就會有耦合性,我們可以參考案例 2 的方式解耦
不過發送郵件並不會有回傳值,
我們要如何驗証正確性呢?
A:檢查調用次數、參數
圖例解析
在案例 2 的單元測試,
我們透過 STUB 偽造的回傳值完成測試
並執行驗証.
但是在沒有回傳的值的方法中(被稱作MOCK
)
我們只能透過傳遞的參數(如果有多載)
與方法被調用的次數來進行驗証。
重點摘要
- 單元測試要能清楚表達測試的目的(達意)
- 命名
- 減少意外的細節
- 單元測試一次只作一件事
- new 本身就是一種邏輯 一種偶合
- static 是一種高偶合
- 繼承也是高偶合,能使用繼承的情境很少
- A is a B 通常只有這種情境才適合繼承
- STUB & MOCK
- STUB 會有回傳值,可以在 Unit Test 作驗証(ASSERT)
- MOCK 沒有回傳值,可以在 MOCK 本身中 作驗証(ASSERT)
其它
- SLIM
- 注入相依的幾種方式
- Pool
- Constructor
- Property
- 書單 : XUnit Test Patterns
直播影片
如果連結失效,煩請告知.
文章內容如有謬誤,煩請指正.
(fin)