這篇文章是結(jié)合"The Art of Unit Testing"一書及業(yè)內(nèi)一些單元測試大牛們的總結(jié)整理而成。主要內(nèi)容是實(shí)際工作中如何運(yùn)用一些技巧和方法編寫出可靠、可讀、可維護(hù)的優(yōu)秀單元測試用例。
優(yōu)秀單元測試的定義
單元測試:一段** 自動化** 的代碼 ,這段代碼調(diào)用被測試的** 工作單元 ,之后對這個工作單元的 單個最終結(jié)果 的某些假設(shè)進(jìn)行檢驗(yàn)。單元測試幾乎都是用 單元測試框架進(jìn)行編寫。單元測試容易編寫,快速運(yùn)行,可自動化,可靠,可讀,可維護(hù),結(jié)果穩(wěn)定**。
集成測試:對一個工作單元進(jìn)行的測試,這個測試對被測試的工作單元沒有完全的控制,并使用該單元的一個或多個真實(shí)依賴物,例如數(shù)據(jù)庫、系統(tǒng)時間、系統(tǒng)文件等
工作單元:從調(diào)用系統(tǒng)一個公共方法到產(chǎn)生一個測試可見的最終結(jié)果,其間這個系統(tǒng)發(fā)生的行為。一個工作單元既可以是小到只包含一個方法,也可以大到包含實(shí)現(xiàn)某功能的多個類或方法。
最終結(jié)果:是指被調(diào)用的公共方法返回一個值;或者在方法調(diào)用前后,系統(tǒng)的狀態(tài)或行為有可見的變化,這種變化不需要查詢私有狀態(tài)即可判斷;又或者是** 調(diào)用了一個不受測試控制的第三方系統(tǒng) **,這個第三方系統(tǒng)不返回任何值,或者返回值已被忽略。
編寫可靠的測試
所謂可靠性是指單元測試本身是正確的,即該失敗的時候失敗,該成功的時候成功。只有保證單元測試的可靠性,才能讓開發(fā)人員信任該單元測試,不會為了以防萬一進(jìn)行調(diào)試等其他工作。
在"單元測試的藝術(shù)"中,作者給出了一些簡單的原則和技術(shù)幫助編寫可靠的測試。
- 依據(jù)實(shí)際情況合理地刪除或修改單元測試
如果確定是測試缺陷,而不是產(chǎn)品缺陷(被測試代碼缺陷)時,需要立刻修改相關(guān)單元測試代碼;如果被測試的產(chǎn)品代碼的語義或者API變更導(dǎo)致測試失敗,這時是需要修改測試,使用新的語義;如果看到測試名含義不清或者單元測試的可維護(hù)性差就應(yīng)該在保證單元測試基本功能前提下修改測試名稱或者重構(gòu)測試;如果同一個功能多個單元測試,請刪除重復(fù)測試。 - 避免在單元測試代碼中包含邏輯
包含邏輯的測試是指測試代碼中包含switch、if/else、for/while等控制流語句。這樣的測試可讀性差,代碼脆弱,測試代碼的復(fù)雜度高,容易包含缺陷,測試結(jié)果不容易重現(xiàn)。 - 每個單元測試只測試一個關(guān)注點(diǎn)
所謂的一個關(guān)注點(diǎn)就是指一個工作單元的一個最終結(jié)果:一個返回值、系統(tǒng)狀態(tài)的一個改變、對第三方對象的一個調(diào)用。測試多個關(guān)注點(diǎn)一方面不利于測試命名,另一方面很多單元測試框架中,一個失敗斷言就會拋出一個特殊類型的異常,后面代碼不會繼續(xù)執(zhí)行,這樣不利于收集測試失敗原因。 - 區(qū)分單元測試和集成測試
- 用代碼審查確保代碼覆蓋率
如果你做了代碼審查、測試審查、確保測試優(yōu)秀而且覆蓋了所有代碼,那么就可以避免犯簡單愚蠢的錯誤,同時也可以從持續(xù)的學(xué)習(xí)中獲益。
編寫可讀的測試
單元測試可以看做是一個婉婉道來的故事,這個故事是講給下一代開發(fā)者聽,故事的內(nèi)容就是這個應(yīng)用程序的組成及其流程。既然是故事,就一定要形象生動,易于理解。那么可讀性就是指如何確保其他開發(fā)者能夠理解他們要做的工作,以便維護(hù)產(chǎn)品代碼和測試。
單元測試的可讀性其實(shí)和代碼可讀性在很多方面類似,下面就逐一討論這些方面。
- 單元測試的命名標(biāo)準(zhǔn)
合理地命名測試,主要目的是為了使后來的開發(fā)者從為了理解測試而閱讀代碼的負(fù)擔(dān)中解脫出來。測試名應(yīng)該包含三部分:被測試方法名、測試場景(即測試使用的條件)、預(yù)期行為(即被測試方法的最終結(jié)果)。 - 單元測試中的變量命名規(guī)范
單元測試除了主要的測試功能之外,它還為API提供某種形式的文檔。通過合理命名變量,幫助閱讀測試的人可以盡快理解你要驗(yàn)證什么(從而更加理解產(chǎn)品代碼中想要實(shí)現(xiàn)什么功能)。 - 斷言和操作分離
- 避免濫用setup和teardown
比如在setup中準(zhǔn)備stub和mock對象,這種情況就會導(dǎo)致閱讀測試的人意識不到測試中使用了模擬對象,也不知道這些模擬對象預(yù)期是什么。
編寫可維護(hù)的測試
可維護(hù)性是大多數(shù)單元測試面臨的最大挑戰(zhàn)。隨著時間累計(jì),單元測試似乎越來越難維護(hù)和理解,被測代碼一個微小的改動,似乎都會使某個測試失敗。那么怎么能夠盡量降低可維護(hù)性的成本呢?
- 只測試公共契約,避免測試私有或者受保護(hù)的方法
私有方法可以看做是系統(tǒng)內(nèi)部契約,這個內(nèi)部契約是動態(tài),在系統(tǒng)重構(gòu)時可能會被隨時修改,因此針對這些內(nèi)部契約的單元測試也很可能會失敗。而內(nèi)部契約最終都會被一個公共契約(公共方法、整體功能)所調(diào)用,也就是說任何私有方法通常都是一個更大的工作單元的一部分。 - 去除重復(fù)代碼
可以使用輔助方法或者setup來去除重復(fù)代碼的問題 - 實(shí)施測試隔離
測試隔離是指每個測試都只生活在自己的小世界中,它與其他測試之間沒有任何依賴關(guān)系,甚至不知道其他測試存在。
下面列舉幾種常見的測試隔離的反模式。
1、測試結(jié)果依賴測試執(zhí)行的順序
2、測試調(diào)用其他測試方法
3、測試中使用的共享資源(內(nèi)存或外部資源)沒有得到清理或回滾 - 避免對不同關(guān)注點(diǎn)多次斷言,盡量使用參數(shù)化測試或者對每個關(guān)注點(diǎn)設(shè)計(jì)單獨(dú)的測試用例
- 避免過度指定
常見的過度指定的例子。
1.對系統(tǒng)內(nèi)部契約進(jìn)行斷言
2.使用過多的模擬對象
3.精確匹配