易于測試的代碼
軟件IC是人們在討論可復用性和基于組件的開發時最喜歡使用的比喻。意思是軟件組件應該就像集成電路一樣進行組合,這只有在你使用的組件已知是可靠地時候才能行之有效。
芯片在設計時就考慮了測試——不只是在工廠,在安裝時,而且也是在部署現場進行測試。更加復雜的芯片和系統可能還擁有完整的Built-In-SelfTest(BIST)特性,用于在內部運行某種基礎級的診斷;或是擁有Test Access Mechanism(TAM),用以提供一種測試裝備,允許外部環境提供激勵,并收集來自芯片的響應。
我們可以在軟件中做同樣的事情,與我們的硬件同事一樣,我們也需要從一開始就把可測試性(testability)構建進軟件中,并且在把各個部分連接在一起之前對每個部分進行徹底的測試。
硬件的芯片級測試大致等價于軟件中的單元測試(unit testing)——在隔離狀態下對每個模塊進行測試,目的是檢驗其行為。一旦我們在受控的(甚至是人為的)條件下對模塊進行了徹底的測試,我們就能夠更好地了解模塊在廣闊的世界上將怎樣起反應。
軟件的單元測試時對模塊進行演練的代碼。在典型情況下,單元測試將建立某種人工環境,然后調用被測試模塊中的例程。然后,它根據已知的值,或是同一測試先前返回的結果(回歸測試),對返回的結果進行檢查。
隨后,當我們把我們的“軟件IC”裝配進完整系統中時,我們將有信心,各個部分都能夠如預期的那樣工作,然后我們可以使用同樣的單元測試設施把系統當做整體進行測試。
但是,在我們走那么遠之前,我們需要決定在單元級測試什么,在典型情況下,程序員會隨便把一些數據扔給代碼,就說已經測試過了,應用“按合約設計”后面的思想,我們可以做得好得多。
針對合約進行測試
我們喜歡把單元測試視為針對合約的測試。我們想要編寫測試用例,確保給定的單元遵守其合約。這將告訴我們兩件事情:代碼是否符合合約,以及合約的含義是否與我們所認為的一樣。我們想要通過廣泛的測試用例與邊界條件,測試模塊是否實現了它允諾的功能。
我們為什么要這么費事?最重要的是,我們不想制造“定時炸彈”——呆在周圍不被人注意,卻在項目最后的尷尬時刻爆炸的東西。通過強調針對合約進行測試,我們可以設法盡可能多的避免那些“下游的災難”(downstream disaster)。
提示
Design to Test 為測試而設計
當你設計模塊,或是單個例程時,你應該既設計其合約,也設計測試改合約的代碼。通過設計能夠通過測試,并履行其合約的代碼,你可以仔細地考慮邊界條件和其他非如此不會發現的問題。沒有什么修正錯誤的方法比一開始就避免發生錯誤更好。事實上,通過在你實現代碼之前構建測試,你必須在你確定采用某個接口之前先對它就行試驗。
編寫單元測試
模塊的單元測試不應被扔在源碼樹的某個遙遠的角落里。他們須放置在方便的地方。對于小型項目,你可以把模塊的單元測試嵌入在模塊自身里。對于更大的項目,我們建議你把每個測試都放進一個子目錄。不管是哪種方法,要記住,如果你不容易找到它,也就不會使用它。
通過使測試代碼易于找到,你是在給使用你代碼的開發者提供兩樣 無價的資源:
1. 一些例子,說明怎樣使用你的模塊的所有功能。
2. 用以構建回歸測試,以驗證未來對代碼的任何改動是否正確的一種手段。
讓各個類或模塊包含自己的單元測試很方便(但卻并非總是可行)。例如,在Java中,每個類都有自己的main,除了在應用的主類文件里,所有的main例程都可用于運行單元測試,當應用者自身運行時它將被忽略。這樣做的好處是,你交付的代碼仍然含有測試,可用于在現場對問題進行診斷。
在C++中,通過使用#ifdef有選擇的編譯單元測試,你可以(在編譯時)獲得同樣的效果。
但是只提供單元測試還不夠,你還必須運行它們,并且經常運行它們,如果類偶爾通過了測試,那也是有幫助的。
使用測試設備
因為我們通常會編寫大量測試代碼,并進行大量測試,我們要讓自己的生活容易一點,為項目開發標準的測試設備(testing harness)。前一節給出的main函數是非常簡單的測試裝備,與之相比,我們通常需要更多的功能。
測試裝備可以處理一些常用操作,比如記錄狀態,分析輸出是否符合預期的結果,以及選擇和運行測試,裝備可以由GUI驅動,可以用項目的其他部分所用的語言編寫,也可以實現為makefile或Perl腳本的組合。
在面向對象語言和環境中,你可以創建一個提供這些常用操作的基類。各個測試可以對其進行繼承,并增加專用的測試代碼。你可以使用Java中的標準名稱約定和反射,動態的創建測試列表。這一技術是遵循DRY原則的好方法——你無需維護可用測試的列表。但是你出發前編寫自己的裝備前,你可以研究一下Kent Beck和Erich Gamma的xUnit。他們已經完成了這項艱苦的工作。
不管你決定采用的技術是什么,測試裝備都應該具有以下功能:
●用以指定設置與清理(setup and cleanup)的標準途徑。
●用以選擇個別或所有可用測試的方法。
●分析輸出是否是預期(或意外)結果的手段。
●標準化的故障報告形式。
測試應該是可以組合的,也就是說,測試可以由子組件的子測試組合到任意深度。通過這一特性,我們可以使用同樣的工具,同樣輕松地測試系統的選定部分或整個系統。
構建測試窗口
即使是最好的測試集也不大可能找出所有的bug;工作環境的潮濕、溫暖的狀況似乎能把它們從木制品中帶出來。
這就意味著,一旦某個軟件部署之后,你常常需要對其進行測試——在現實世界的數據正流過它的血脈時。與電路板或芯片不同,在軟件中我們沒有測試管腳(test pin),但我們可以提供模塊的內部狀態的各種視圖,而又不使用調試器(在產品應用中這可能不方便,或是不可能)。
含有跟蹤消息的 日志文件就是這樣一種機制。日志消息的格式應該正規、一致,你也許想要自動解析它們,以推斷程序所用的處理時間或邏輯路徑。格式糟糕或不一致的診斷信息就像是一堆“嘔吐物”——它們既難以閱讀,也無法解析。
了解運行中的代碼內部狀況的另一種機制是“ 熱鍵”序列。按下特定的鍵組合,就會彈出一個診斷控制窗口,顯示狀態消息等信息。你通常不會想把這樣的熱鍵透露給最終用戶,但這對于客戶服務人員卻非常方便。
對于更大、更復雜的服務器代碼,提供其操作的內部視圖的一種漂亮技術是使用 內建的web服務器,任何人都可以讓Web瀏覽器指向應用的HTTP端口(通常使用的是非標準端口號,比如8080),并看到內部狀態、日志條目、甚至可能是某種調試控制面板,這聽起來也許難以實現,其實并非如此。你可以找到各種現代語言編寫,可自由獲取、可嵌入的HTTP Web服務器。
測試文化
你編寫的所有軟件都將進行測試——如果不是由你和你們團隊測試,那就要由最終用戶測試——所以你最好計劃好對其進行徹底的測試,一點預先的準備可以大大降低維護費用、減少客戶服務電話。
盡管有著黑客的名稱,Perl社區對單元測試和回歸測試非常認真,Perl的標準模塊安裝過程支持回歸測試(%make test)。在這方面Perl自身并無任何神奇之處,Perl使得比較和分析測試結果都變得更為容易,以確保順應性(compliance)——測試被放在指定的地方,并且有著某種預期的輸出。測試是技術,但更是文化;不管所用語言是什么,我們都可以讓這樣的測試文化慢慢滲入項目中。
posted on 2014-05-08 16:08 順其自然EVO 閱讀(259) 評論(0) 編輯 收藏 所屬分類: 測試學習專欄