前言
在工作中或者在面試中,我經常碰到的開發人員就是對單元測試不重視,這一類基本上都表現出了一種“無知的自信”,總覺得自己寫的代碼質量很高,直到一次次蟲子(Bug)把自己咬的頭破血流時,才發現原來自己的代碼已經到了剪不斷理還亂的狀態,而每一次修改一個bug,都需要走一遍“墨鏡迷宮” (看上圖)。還有很多人知道單元測試或者寫出了單元測試,但是就是寫了一個方法,上面標注了一個[Test]屬性而已,甚至很多的人單元測試上面標注的是[IgnoreTest], 每次看見這些,我都深深的感到推行單元測試之路是艱難的,是遙遠的,但是我依然堅信是是渴望也可及的,只要有着深深的信念,堅強的意志,無謂的勇氣,一頭扎進去泥巴堆里,假以時日,當大雨來臨,必將帶走泥巴,從此你拔劍揚眉,哦,你不用拔劍了,因為你就是劍。。。
為了讓更多人能夠拔劍揚眉,也為了我們公司剛入職的新人做一些培訓,我精心准備了單元測試的一些知識,在此為你奉上,我盡量用簡短的語句來描述,如果你不清楚我說的某一些點,那么歡迎你發郵件給我 wangdeshui@outlook.com,我可以針對集中的點發篇文章,如果你想知道我說的所有點怎么實踐,那就聯系我,試試加入到我們公司來。
- 前言
- 什么是單元測試
- 為什么要使用單元測試
- 單元測試難以推動的原因
* 太花時間
* 測試不是我的工作
* 系統可測試性差 - 單元測試最佳實踐
* 實踐一: 三到五步
* 實踐二: 運行快速
* 實踐三:一致性
* 實踐四:原子性
* 實踐五:單一職責
* 實踐六:獨立無耦合
* 實踐七:隔離外部調用
* 實踐八: 自描述
* 實踐九: 單元測試邏輯
* 實踐十: 斷言
* 實踐十一:產品代碼 - 最后,單元測試常用技術及工具
什么是單元測試
單元測試是開發者編寫的一小段代碼,用於檢驗被測代碼中的一個很明確的功能是否正確。通常而言,一個單元測試是用於判斷某個特定條件(或者場景)下某個特定函數的行為。
執行單元測試,是為了證明某段代碼的行為確實和開發者所期望的一致。因此,我們所要測試的是規模很小的、非常獨立的功能片段。通過對所有單獨部分的行為建立起信心。然后,才能開始測試整個系統。
為什么要使用單元測試
- 單元測試使工作完成的更輕松
- 單元測試使你的設計更好
- 大大減少花在調試上的時間
- 能幫助你更好的理解代碼
沒有單元測試
- 任何代碼都是在假定其他代碼是正確無誤的情況下編寫的。
- 修改一處代碼時無法得知會對其他代碼產生怎樣的影響。
- 任何一處改動都需要進行功能級別的整體調試。
單元測試難以推動的原因
太花時間
很多人認為單元測試很花時間,但是想想我們在下面幾點話的時間,我經常看到為了測試一個簡單的API方法,我們很多人必須讓前端跑起來,甚至自己寫一個客戶端才能調用
- 調試上花的時間
- 對自認為正確的代碼,花了多少時間確認代碼是正確的。
- 定位Bug所耗的時間
測試不是我的工作
很多人認為測試不是自己的工作,但是想一想每次測試提出一個bug所花的時間,以及你改bug所化的時間,所以下面2點是很重要
- 內在質量的重要性
- 測試應該是輔助,好的軟件是開發設計出來的,不是測試出來的
系統可測試性差
- 系統耦合度很高,我們需要提高我們的團隊的設計能力。
單元測試最佳實踐
實踐一: 三到五步
- SetUp
- 輸入
- 調用
- 輸出
- TearDown
實踐二: 運行快速
為什么?
單元測試運行很頻繁,是輔助開發的,在開發過程中運行,如果慢影響很大
多快較好?
- 單個測試小於200ms
- 單個測試套件小於10s
- 整個測試小於10分鍾
實踐三:一致性
任何時候同樣的輸入需要同樣的結果
Date date=new Date()
Random.next()
這樣的代碼都需要Mock掉,不然時間每次都不同,結果就會不一樣。
實踐四:原子性
** 所有的測試只有兩種結果:成功和失敗**
不能部分測試通過
實踐五:單一職責
一個測試只驗證一個行為
** 測試行為,不要測試方法 **
- 一個方法,多個行為 -----> 多個測試
- 一個行為,多個方法 ----- 一個測試
這里的一個行為,多個方法一般指這個方法調用private, protected, getters, setters - 多個Assert只有在測試同一個行為時可以接受
實踐六:獨立無耦合
單元測試之間無相互調用
- 單元測試執行順序無關
- 不同的順序無影響
單元測試之間不能共享狀態
比如一個測試里設置了一個屬性值,然后在另外一個測試里用,如果必須共享可以放到Setup里
實踐七:隔離外部調用
- 單元測試需要快速運行,且每次結果一致,所以需要隔離一切對外部的調用。
- 不使用具體的其它真實類,就是不要new
- 不讀數據庫
- 不讀網絡
- 不讀外部文件
- 適當時候可以構造一個相同的內部文件來Mock
- 不依賴本地時間
- 不依賴環境變量
實踐八: 自描述
- 單元測試是開發級文檔
- 單元測試是方法的描述
實踐九: 單元測試邏輯
- 單元測試必須容易讀和理解的
- 變量名,方法名,類名
- 無條件語句,無Switch
辦法:分解if到多個測試,所有的輸入都是已知的,所有的結果都是一定的(Mock) - 無循環語句
- 無異常捕捉
** 測試預知的異常,用ExpectedException方法 **
實踐十: 斷言
-
斷言信息最好包含Business Information
-
斷言信息包含出錯的具體信息如果失敗
-
適當時候可以封裝自己的Assert
比如:Assert.IsProgrammer(Jack) Return Jack. Cancooking() && Jack.CanCoding()
實踐十一:產品代碼
- 產品代碼無測試邏輯
不能有:
If(global.IsTest){…}
- 測試代碼和產品代碼要分離
- 不要在產品代碼里有任何只供測試用的代碼
- 使用依賴注入
最后,單元測試常用技術及工具
下面是.NET程序常用的單元測試需要的技術和工具,其它語言請自信比對。
- 面向接口編程
- 依賴注入(Castle, Unity, Ninject)
- Moq
- 測試工具(xUnit)
- .Net Nunit
- 代碼覆蓋率測試工具Ncover
- 自動運行測試輔助工具NCrunch