如何編寫具有可測試性的代碼
很多人在開發過程中都強調測試驅動開發,單元測試,代碼測試覆蓋率。那麽為什麽大家要強調這些?這些工作非做不可麽? 其實並非絕對。不論是驅動測試開發,還是代碼測試覆蓋率,本質上都只是方法,而不是目的。人們的真正的目的,是編寫出優秀的,高質量的具有可維護性的,能夠很好擴展的代碼。
問題來了。
什麽是具有可測試的代碼?
所謂具有可測試的代碼,是指能夠很輕松的執行各種測試的代碼。
具有可測試性的代碼有什麽特點?
1. 控制性。
控制性是指測試者給在被測試的軟件提供固定的輸入數據的方便程度。換句話說就是軟件本身接受定義明確的參數,並且這些參數可由測試者靈活的傳入,軟件在接受到這些參數後通過一系列運算返回固定的結果。任何軟件都應該清楚的表明自己需要什麽參數,以及將會生成什麽返回值。此外任何軟件都應該應該抽象它的依賴,譬如參數或底層模塊,並為外部調用者提供隨意註入的方式。當然軟件
2.可見性。
可見性是指測試者觀察正在測試的軟件的當前狀態以及它所產生的任何輸出的能力。換句話說就是軟件應該將內部運算的狀態(一般是指錯誤狀態)和輸出結果清晰明確的告知測試者。可見性一般都是通過方法執行後驗證後置條件完成。
驗證後置條件與契約式設計有關。所謂的契約式設計,是指把組件之間的交互描述成契約,權利和義務得到明確的表達和強制實施。在.net環境下,可以通過.net Framework4新增的Code Contracts庫創建軟件契約。
3.簡約性。
一般而言,簡約性對任何系統在任何情況下都是一個正面的屬性,測試毫無疑問也不例外。
編寫可測試性代碼與編寫測試的關系
首先,編寫可測試性代碼與編寫測試有本質區別。可測試性是一個特征,代表著更好的代碼質量,是更容易擴展和維護的代碼。而測試是流程,它的目標是驗證代碼是否滿足人們的預期,驗證代碼能夠按照我們的要求毫無差錯的進行工作。
其次,二者有很強的關聯性。如果說可測試性的代碼是目的,那麽編寫測試就是通向目的的重要方法。
如何執行得到可測試的代碼和程序
啰嗦了半天,終於到正題了。那麽如何得到編寫具有可測試性的代碼和程序呢?
1.堅持面向對象編碼原則。
編碼原則多年以來,已被無數人強調過無數遍了,但是我還是要提及一下,因為短短的幾行原則,真真正正是軟件編碼的金科玉律,不二良言。
- 單一責任原則
- 開放/封閉原則
- 裏氏代換原則
- 接口分離原則
- 依賴反轉原則
具體的解釋,我在此不再贅述。但是不得不多提幾句的是,原則的使用,切忌照本宣科。
對象建模並不容易,而且也不是精確科學。原則的存在大多數情況下是告訴我們做事的方式,為我們提供正確的方向。建模的重點是要找到正確的原則組合。如果將原則全面照本宣科的進行實施,這些原則會引導我們持續重構,直到我們突然發現在超過某個零界點後工作量繼續增加,而得到的好處卻開始減少,甚至得不償失。
2.堅持編碼建議。
同樣是老生常談,在此稍微提及一下,不做進一步解釋。
- KISS(keep it simple,stupid)
- YAGNI(you are not gonna need it)
- DRY(don‘t repeat yourself)
- TDA(Tell don‘t ask)
同樣的,這些建議也要在項目中酌情使用,照本宣科只會違背初衷。
3.使用設計模式
21中設計模式,我在此不做贅述,有大把的資料和文章對Gof進行了詳細描述和解讀。
我在此說明一下,我對設計模式的使用方式的理解。
設計模式不是必須執行命令,不應該武斷的解讀,它也不是靈丹妙藥,不可能突然出現拯救項目於水火。我們不能為了使用設計模式而使用設計模式,不是我們選擇一個設計模式,並把它運用到項目中。正確的方式應該是:我們遇到一個問題,然後把問題匹配到設計模式的一種。
當我們不知道如何匹配時,我們應該運用面向對象設計原則,不停的優化我們的代碼,對其進行抽象,泛化。不久之後,我們就會發現我們的解決方案已經無限的接近於某一個設計模式了,然後我們欣然的選擇這個模式,並利用其改造我們的代碼。這個方法屢試不爽。為什麽會這樣呢?因為設計模式本身,就是無數前輩大牛的抽象過後的解決方案啊。
4.堅持測試驅動開發。
回到了我們之前提到的可測試性代碼與測試之間的關系了。我們通過編寫測試,得到可測試的代碼,寫測試的目標是為了得到好代碼,而不是好測試。在編寫代碼之前,先通過討論,得到功能點的輸入輸出,並提前編寫相關的測試代碼,然後在測試代碼的約束下,得到更加高內聚低耦合的代碼設計,進而完成相關的功能編碼。
編寫了測試,我們僅僅完成了測試驅動開發的一部分。測試僅僅是基礎工程,我們後期可以依托測試,進行持續的重構,而絲毫不用擔心破壞了原有的代碼邏輯和返回結果。重構,才是堅持測試驅動開發的核心部分。而持續的重構,最終我們得到的就是設計良好的代碼,還附帶一堆高代碼覆蓋率的測試作為獎勵。
具體的測試方法,包括不限於:單元測試,集成測試,驗收測試。我們還可以使用各種仿冒對象和虛擬對象來簡化測試,在此不再贅述。
參考引用:
《Microsoft.NET企業級應用架構設計》【意】Dino Esposito
如何編寫具有可測試性的代碼