1 為什麼要關心可測試性
很多公司很多人在實踐單元測試中總覺的很困難的一個很重要的原因就是其程式碼本身不具有可測試性。他們往往會走入一個誤區。面對一個幾千行、邏輯混亂的方法而抓耳撓腮的想著用十八般武藝,各種框架去寫這個方法的單元測試,而最終不得不以失敗而告終,耗費了大量的時間卻徒勞而無功。其實是他們忽略了這個方法的本身是不具有可測試性的。
因此我們在要對一個方法進行單測之前,一定要先看一下它是不是具有可測試性,如果不具有,那麼我們應該先對其進行重構以提高其可測試性。
在可測試的設計中,你應該很容易為程式碼的每一段邏輯(迴圈、i語句和 switchi等)快速編寫一個單元測試,這些單元測試具有如下屬性
- 執行速度快
- 相互隔離,即每個測試可以獨立執行,或者作為一組測試的一部分執行,可以按任何順
- 不需要進行外部配置
- 產生穩定的通過或失敗結果。這些就是FCC屬性:快速、隔離、無需配置和穩定(Fast, Isolated, Configuration-fre,andConsistent)。
如果編寫這樣的測試很困難,或者需要很長時間,那這個系統就不是一個可測試的,如果把測試看做系統的一個使用者,可測試性設計就成為一種思維方式。
2 可測試性設計技巧
如果你要測試的方法真的有幾百行上千行,那麼我建議先用重構一書中的方法去解決它。其次關於可測試性在實踐過程中是有一些技巧的。
2.1 方法可重寫
在Java中,方法預設就是虛擬方法,可重寫的。而在NET中,如果想要替換一個方法的行為,你需要明確地把方法設定為虛擬方法,才能進行重寫。因此我們可以在設計之初就儘量把方法用關鍵字virtual標記。
2.2 面向介面設計
如果我們的程式碼都能夠保持面向介面設計的原則,意味著我們的依賴都是很容易被替換的,可以讓我們在測試中年很容易取創造真實物件的偽物件。
2.3 避免密封類
密封類就是將一個類封閉起來,斷其子孫的一種方式。
密封方法則不是為了防止繼承而是防止重寫,而且它是為了重寫基類的虛方法並提供具體的實現,同時防止其後繼類(派生類)再次重寫該虛方法
無論是我們的被測方法還是其依賴的方法屬於一個密封類,那麼意味著這個方法是不能被重寫的,在測試中也就不能去替換它。因此應該儘量避免使用密封類
2.4 避免在方法內初始化物件
很多人在寫程式碼的時候習慣於在一個方法內部初始化物件,如下面這樣
public void GetName(int userId){
//方法內部初始化類
return new User(userId).GetName();
}
這樣的設計其實是違背了程式碼的低耦合原則的,也不具有可測試性,因此我們在開發中應該儘量避免在方法中初始化另一個物件。對於外部依賴的物件可以使用依賴注入的方式。
2.5 避免直接呼叫靜態方法
要在測試中替換一個靜態方法的行為,是非常困難的。
要處理這種情況,我們可以使用抽取和重寫的方法進行重構,把這個靜態方法抽象出去。
一個更為極端的做法是:避免使用任何的靜態方法。這樣的話,每一段邏輯都是一個類例項的一部分,使得這段邏輯更容易替換。有些進行單元測試或者測試驅動開發的人不喜歡使用單例,原因之一就是單例缺少可替換性。單例是靜態的公共共享資源,很難重寫它們。
要完全避免使用靜態方法可能會過於困難,但是你可以嘗試在應用程式中儘量少使用單例或者靜態方法,這樣在測試時會變得容易一些。
2.6 避免在建構函式和靜態建構函式中包含邏輯程式碼
無論是建構函式還是靜態建構函式內的邏輯同樣是我們無法在測試中重寫控制它的,因此我們需要避免在建構函式和靜態建構函式中包含邏輯程式碼。
關於可測試性設計雖然是有著許多技巧,但如果你掌握了程式碼編寫的內容心法"SOLID原則",並且熟練應用於編碼中,其實你的程式碼絕大多數都是具有良好的可測試性的。
3 可測試性設計的缺點
可測試性固然可以更加方便的讓我們對程式碼進行單元測試,但有些時候也會給我們帶來一些“麻煩”。因此它也是一個頗有爭議的話題。有些人認為可測試性是好的設計應該具有的特徵之一。也有些認為可測試性會破壞原有的設計帶來一些副作用。
那麼可測試性設計會帶來哪些“麻煩”呢
3.1 工作量
大多數情況下,設計時以可測試性為目標會增加工作量,因為比起不考慮可測試性的設計比起可測試性設計需要編寫更多的程式碼。
3.2 複雜度
可測試性設計有時會讓人覺得,它把簡單的事情過於複雜化了。有些介面的使用讓你感覺很彆扭或者設計公開了你未曾考慮過的類行為語義。而且,當使用了很多介面把東西進行抽象之後,如果你要瀏覽基礎程式碼找到一個方法的真正實現程式碼,會更加困難和麻煩。
3.3 破壞更好的設計封裝
從一些可測試性的技巧我們可以知道有些時候為了讓程式碼更具有可測試性,會破壞一些原有的設計原則。
正如人月神話一書中所說,在軟體開發這一行業中沒有銀彈。一門技術或者方法論在給我們提供了一些幫助的同時也會帶來一些問題。這就需要我們在具體使用的過程中****case by case,根據可測試性設計所帶來的優缺點找到一個平衡點!