當設計大型程序的時候,您必須時刻留心不同設計選項對諸如性能和可擴展 性這樣的特征的影響。隨著軟件產品的日漸復雜及其無所不在的部署,軟件的“ 可測試性”也成了更重要的考慮事項。
徹底測試代碼的重要性是顯然的。花在編寫測試和測試代碼上的時間和精力 給您帶來的回報是維護成本的大幅降低。
然而,除非您很小心,否則您花在測試代碼上的精力可能會首先達到花在編 寫代碼上的精力的幾倍!我曾看到程序員們齊心協力地對他們的全部代碼進行單 元測試,結果花在上面的時間使大多數人都以沮喪而告終。
幸運的是,沒有必要這樣。在您設計軟件的時候應用一些基本原則,編寫易 於測試、甚至使測試成為樂趣的代碼是可能的。
跟其它編碼原則一樣,這些原則也不是不容置疑或不可改變的教條。有時候 打破這些規則也是必要的。因此,理解每條原則背後的動機和判斷何時這些動機 不適用(或應讓位給更關心的問題)的能力是很重要的。
原則 1. 到 GUI 視圖的外面去
盡可能把代碼移到 GUI 視圖的外面。然後各種 GUI 動作就能成了模型上的 簡單方法調用。為什麼您需要這樣做呢?
對 GUI 測試者來說,通過方法調用測試功能比間接地測試功能容易的多。
另一個好處是它使修改程序功能而不影響視圖變的更容易。
當然,視圖中也可能存在錯誤。在理想情況下,對程序的測試將同時檢查模 型和視圖。
原則 2. 使用類型進行錯誤檢查
類型是您的朋友 ― 盡可能多地用類型系統自動檢查錯誤。
類型能在程序運行之前自動捕捉程序中的錯誤。沒有靜態類型檢查的話,類 型錯誤將作為破壞者逗留在您的程序中,直到恰當的執行路徑碰巧把它揭露出來 為止。
最大限度地發揮使用類型的長處是棘手的。通常,一組數據結構可以在一個 抽象級別上一起使用,或者被分出,成為一個單一的、更高抽象級別的一個新的 相關數據類型。
事實上,編程語言自身的歷史可以看成是可以編程的抽象級別的逐漸提高。 匯編語言提供了比特到整數和浮點數的抽象。接下來是記錄和函數抽象,然後又 是諸如對象、類、線程以及異常這樣的抽象。
在每一抽象級別上,達到與更高級別抽象一致的功能是可能的,但那實質上 僅僅是耗費更多精力,冒更多的錯誤風險。
在面向對象語言(其它現代語言也一樣)中,一個程序員在設計抽象上有很 大的靈活性。在哪個抽象級別上設計程序就成了基於折衷的決定,比如由抽象級 別提供的更多的健壯性和由於不能在更低抽象級別上工作而帶來的表達性(有時 是性能)的損失。
通常,高級別抽象帶來的健壯性和簡單性的價值很少被其它考慮事項超過。
原則 3. 使用調節器避免“故障線路”(fault line)
我用“故障線路”來指獨立組件之間的接口,獨立組件之間和組件與其相應 子組件之間相比,很少有交互。這種故障線路的一個典型示例是 GUI 視圖和它 的模型之間的接口。其它示例包括在編譯器中處理的不同階段之間的接口或操作 系統的內核和用戶界面之間的接口。
找出程序的故障線路,然後用具有轉發功能的調節器快速訪問聚合組件。
沿著故障線路隔離測試每個組件通常更容易。但如果每個組件暴露的對象有 很多,或者組件中您想測試的一些對象只有通過多個嵌套引用才能訪問,那麼測 試就會變的很乏味。
不用隔離測試,而是擁有您在它上面調用您想測試的各種方法的單個調節器 對象通常是有幫助的。這個對象然後能把這些方法調用轉發到適當的地方。
沿著相同線路,設計和自己的測試代碼串聯在一起的程序組件接口是有益的 。這將使您把注意力集中在使這些接口盡可能簡單上。
原則 4. 方法:小型簽名和缺省參數
使用小型方法說明和重載帶缺省方法參數的方法將使您在測試中調用這些方 法變的愉快的多。否則,在測試這些方法時您將不得不構造額外參數。如果參數 很大,那麼將很快導致代碼膨脹。更糟的是,它會誘使您編寫比在其它情況下更 少的測試。
原則 5. 訪問器不應修改內存狀態
請在您的測試中使用不修改內存狀態的訪問器來檢查對象狀態。
在某些方面,測試和實驗室試驗相似。它們都想證明特定假設有效。如果特 定檢查動作改變了該領域的狀態,那麼要這樣做會變得困難的多。
與量子力學領域不同,計算機進程的狀態可以不修改就被檢查。使用這種原 則對您有好處。
原則 6. 用接口說明外部程序組件
用接口說明外部程序組件使得我們可以容易地在測試案例中模擬這些組件。
這條原則能節省大量時間,特別是當外部組件的實現還未完成時。通常,大 多數基本組件都不能准時可用。如果這些組件不在適當位置您就不能測試您自己 的代碼的話,那麼您就在朝災難走去。您的客戶不會關心您只有兩個小時來集成 遲到了兩周的組件。他們知道的全部就是整套產品被延期了和這是違約的。
原則 7. 優先編寫測試代碼
優先編寫測試代碼。這是標准的 XP 方法,但卻總有一種忽視它的誘惑。
每次我屈服於這種誘惑時,我都感到後悔。假設您正努力生產正確的代碼, 那麼您 好象能從推遲編寫測試代碼中節省的時間其實只是一個幻想。
注意:這不是說您應該一次性編寫全部測試代碼後,再一次性全部實現。編 寫一些測試代碼,實現它們,再編寫一些測試代碼,再實現它們等等是個更好的 辦法。設計以這種方式得以進展;在實現階段捕捉錯誤並在下一組測試中改正它 。以這種方式編寫測試也更少會使人畏縮。
代碼比您需要的還多?
只需一點點努力,就可能容易地對任何程序進行徹底的測試。當然,不可避 免存在這些原則不適用的情況;於是,看起來好像不可能對功能進行測試。
當出現這些情況時,我盡力退一步地看這個問題,“我怎樣才可能測試這種 代碼?”相反地,我問自己,“我怎樣才能以可測試方式編寫這些代碼呢?”這 種想法上的改變的結果經常是增加了大量 僅僅服務於簡化測試的功能。
什麼?別擔心;出現這種情況完全正常。
就象很多現有的設計模式,它們只是為了增加程序的可擴展性就往程序中添 加很多類(例如 visitor、decorator 等等),開發簡化測試的新模式是可以接 受的。實際上,面向對象語言的很多特征都是為了簡化擴展而包含進去的;為什 麼語言的未來版本(或全新的語言)不應包含簡化測試的特征。
對 Java 語言來說,這已經開始。人們計劃在未來版本中包含很多更強大的 類型系統、斷言(assertion)等等。就象面向對象的語言已經增加了我們重用 和擴展現有代碼的程度,將來,面向測試的設計和特征將幫助我們增強新老代碼 的健壯性。