軟件質量之路(3): 測試驅動開發

測試是如何驅動開發過程的

測試驅動開發起源於XP法中提倡的測試優先實踐。測試優先實踐重視單元測試,強調程序員除了編寫代碼,還應該編寫單元測試代碼。在開發的順序上,它改變了以往先編寫代碼,再編寫測試的過程,而採用先編寫測試,再編寫代碼來滿足測試的方法。這種方法在實際中能夠起到非常好的效果,使得測試工作不僅僅是單純的測試,而成爲設計的一部分。爲什麼這麼說呢?

在編寫程序之前,每個人都會先進行設計的工作。可能有些人的設計比較正式,繪製模型,編寫文檔。有些人的設計只是存在於腦海之中。且不論是設計是精細還是粗糙,你都爲隨後的編碼活動制定了一個標準。這個標準的明確程度和你的設計的細緻程度有關。但應該承認,這個標準是不夠細化的。因爲你的設計不可能精細到代碼級的程度。而標準不夠明確的則會產生一些問題,例如,在編寫代碼的過程中,你還可能會發現原先的設計出現問題,從而中途改變代碼的編寫思路。這將會導致成果難以檢驗,進度難以度量。

既然以設計爲導向的標準不夠明確,不夠具體。那什麼樣的標準纔是合適的呢?只能是代碼。因爲代碼是最明確、最具體的。所以測試優先的本質其實是目標管理。編寫測試代碼其實是在制定一個小目標。這個小目標非常的明確,它規定了你需要設計的類、方法。以及方法需要滿足的結果。這些目標制定完成之後,你纔開始編寫代碼來達成該目標。測試的目標要比設計的目標粒度更小,但是成本上卻更爲經濟。其原因有四:

 

  • 細粒度的設計需要花費大量的成本,雖然CASE工具都提供了代碼自動生成的功能,但結果往往難以令人滿意。所以,設計如果要做到和測試相同的粒度,成本不菲,如果粒度不夠細,指導的意義又不夠。
  • 減輕了測試的工作量。無論是否進行設計工作,測試工作都是不可避免的,先進行單元測試,可以減少後續的測試工作量。
  • 採用測試優先的過程中,設計的粒度較大。因爲測試可以實現一部分的設計工作。這樣,設計上可以節省一些工作量。例如,你不再需要將類圖細化到每個方法。
  • 在編寫測試代碼上花費的成本,會在迴歸測試上得到回報。自動化測試的最大好處就是避免代碼出現迴歸。兩相權衡,編寫測試的代價其實不高。

 

你也許會說,我既不進行如此精細的設計,也不事先編寫測試代碼,這樣的成本不是最低嗎?請注意,我們的前提是在討論高質量的軟件設計。在一些規模較小或是開發人員能力極強的項目中,確實可以如此辦理。但是對於強調質量的大項目,這種處於混沌狀態的開發思路是不可取的。

測試優先是軟件開發中一種細粒度的目標管理方法,通過明確的目標,推動軟件開發的進行。

在業界中,採用測試作爲評價軟件標準的做法是非常常見的。例如,sun公司就專門設計了測試軟件,對各個實現J2EE規範的產品進行測試。使用測試作爲規範的最大好處就是明確、具體。

使用測試代碼建立目標,編寫代碼完成測試目標,再製定下一個目標,如此循環,構成了測試驅動開發的工作流程。在接下去的篇幅中,我們開始討論測試驅動開發中需要注意的一些問題。 


測試必須是自動化的

和自動化測試相對的是手動測試。手動測試有着自動化測試所沒有的優勢。最明顯的就是簡單。任何一位開發人員都能夠進行手動測試。即便是用戶,也很容易掌握手動測試,他們輸入數據,並觀察軟件的反應(輸出),從而判斷行爲是否正確。大部分的手動測試都是對輸入和輸出的檢驗,是一個端到端過程,很能說明問題。

但是手動測試存在的問題比它所帶來的好處要多得多。手動測試可能引入錯誤,人爲的輸入錯誤很容易發生,尤其在數據量大的情況下;大量重複性的手動測試可能成本較高,如果考慮軟件發生改動而需要重複手動測試的情況,這個成本還會更高;手動測試的覆蓋面不廣,只能夠測試系統的輸入和輸出;沒有辦法對組件進行隔離的測試,從而導致發現問題和解決問題的成本都太高。

基於上面的討論,我們應該看到,測試應該做到自動化。雖然一開始測試自動化的成本較高,但是從整個的開發過程來看,自動化測試所產生的價值遠遠超過其成本


自動化測試的範圍

那麼,到底有哪些東西是需要納入到自動化測試的範圍的呢?例如,對於一個典型的分層應用來說,就有數據庫層、數據庫訪問層,業務邏輯層、界面控制層、界面層。這些層次的測試特點各不相同,哪些應該進行自動化測試呢?最理想的情況是全部。測試一切可能是測試的基本原則,讓一切測試都變成自動化則是測試驅動開發的準則。

應該承認,建立自動化測試需要付出成本,有些自動化測試成本較低,有些則較高。例如,對業務方法的自動化測試相對容易,對關聯到數據庫的業務方法的測試則繁瑣一些,因爲你需要處理更多的情況。而界面的自動化測試則較困難,因爲界面涉及到大量的人機交互,手動測試是非常簡單的,但是自動化測試則相當的困難。

那麼,我們就來看看,像界面測試這樣的成本高昂的測試需不需要進行自動化呢?我們拿駐留的Web界面來作爲人機交互界面的範例。

首先,按照分層的原則,界面的層次上不應該擁有業務邏輯,界面層負責的事情是收集用戶的動作,將用戶的動作請求委託給後端的業務層,並對動作進行響應。所以,和業務相關的邏輯已經被剝離到了業務層了。它的自動化測試屬於業務層。同時,我們還發現,在測試的推動下,軟件的結構變得更加的合理。

其次,雖然業務邏輯大量的遷移出界面層,但是界面層中還有狀態,還有控制邏輯。這些因素都是和界面的控制的表現息息相關的。既然有邏輯,就需要測試。根據MVC的思路,界面層中包含了模型、視圖和控制器。模型是對業務層數據的封裝,在J2EE的應用中,可能是普通的JavaBean,也可能是離線的數據封裝,或是簡單的數據集合。視圖負責表現模型,而控制器的職責則比較多,它需要負責處理和檢查請求參數,負責調用業務對象並傳遞請求中所包含的數據,負責創建模型,負責生成視圖並把模型傳遞給它。

所以,在一個真正的MVC界面設計中,測試的重點在於控制器。模型的測試是很容易的,大部分的模型僅僅包含了數據,所以甚至都不需要測試。一個完美的視圖,它應該沒有包含任何的邏輯,僅僅只是將模型以某種方式表現出來而已。一個設計優秀的視圖,可以很容易的進行替換,而不會造成任何的影響。例如,一個JSP視圖可以用一個XSLT視圖進行替換。所以,結構合理的視圖也是不需要測試的(但對頁面要素的檢查是必要的)。注意,這裏的討論也同樣表明,測試驅動設計向合理的方向發展。

大部分的控制器都是基於servlet技術的,servlet技術是典型的容器內應用,這使得Junit不容易使用。而Cactus( http://jakarta.apache.org/cactus/index.html)能夠解決這個問題,它對Junit進行了擴展,提供了大量的預置功能,簡化了request、response、session的使用,Cactus通過Redirector Proxy,建立了一個測試客戶端和測試服務端模型,來實現基於容器內的測試。下圖是典型的Servlet的結構圖:



Cactus的詳細工作機理可以參考這裏( http://jakarta.apache.org/cactus/how_it_works.html)。

除了構造一個容器來進行測試之外,另一種方式則從根本上改變了控制器的測試方法,那就是不將控制器實現在Servlet技術之上。這樣做的好處是測試無需依賴於容器。典型的如springframework( http://www.springframework.org)的Web層就是這樣設計的。

無論採用何種方法,都說明軟件業在對界面的自動化測試已經開始充分的關注,並提出了不少的解決方法了。這樣做的目的無非是兩點:一是通過自動化測試提高軟件質量,二是通過對測試的關注來推動設計的優化。

最後,從開發文化上來看。界面自動化測試的要求意味着開發人員需要先和用戶進行充分的溝通,繪製出滿足需要的頁面,這其實是原型方法的應用,對開發過程是有利的。此外,開發人員需要慎重的思考頁面的設計,保證頁面設計的抗變性和可擴展性,否則,你會發現測試代碼變得非常的不穩定,從而導致一些不必要的麻煩。這種文化將會推動設計的發展。

所以我們看到,在一個成本較高的自動化測試領域,通過合理的設計和引入工具,可以降低自動化測試的成本。而且,在上述的討論中,我們也發現,之所以自動化測試的成本高昂,往往是由於設計不當而造成的。在界面混雜大量的邏輯,導致變化不斷髮生,不但代碼需要修改,測試代碼同樣需要修改,設計的隨便纔是高成本的真正罪魁禍首。也正是因爲這個原因,測試才能夠驅動設計的優化。


測試的分類

單元測試

單元測試是典型的對代碼邏輯的黑盒測試。在測試驅動方法中,不太強調白盒測試(絕大多數的白盒測試都是通過評審進行的)。這樣做的好處是關注接口勝於關注實現,這是一種分析複雜軟件的有效辦法。這一點我們在後續的文章中還會討論。

單元測試是開發人員的職責。一般來說,測試的編碼最好由不同人來負責,避免出現盲點,以提高測試的有效性。但是單元測試的粒度很小,如果進行分工,溝通的成本會相當高。此外,採用測試優先的實踐,對測試進行適當的培訓,也能夠有效的降低單個人的盲點範圍。

單元測試可以加入到小組日構建中,也可以不加入。如果不加入,那麼需要有一種機制來管理單元測試活動。

集成測試

集成測試的粒度和測試的範圍要比單元測試大。我們拿數據庫測試來做例子。現在需要對一個業務對象進行測試,它需要用到持久化機制。在單元測試中,我們將不涉及數據庫而單獨對業務對象進行測試(使用MO技術,下文中討論);但是在集成測試中,我們需要將數據庫的數據一致性也納入進來,所以測試包括數據庫數據的建立,測試業務方法,使數據庫恢復原狀。

集成測試應該是日構建的重要組成部分,即日構建標準中的測試標準。最好將集成測試交給QA部門負責。QA部門的精力可以放在使用或編寫一些工具(Cactus就是典型的集成測試工具),建立標準的測試數據,安排測試計劃等活動上。

接受測試

有時候很難區分集成測試和接受測試。接受測試的關注點是用戶。用戶通過將數據輸入系統來觀察系統的輸出。所以,瞭解用戶的需要並將用戶的需要轉換爲接受測試是接受測試中最關鍵的工作。接受測試處於測試過程的最後環節,是判斷軟件是否滿足用戶需求的試金石。毫不例外。接受測試也應該是自動化的。例如,HttpUnit就是一個自動化接受測試的工具。另外,很多的專業測試工具提供的自動化的腳本測試工具也屬於這個範疇。


測試推進設計

在界面測試的討論中,我們已經認識到測試是可以推動設計的。在這裏我們打算結合具體的技術來討論測試和設計之間的關係。

針對接口設計

測試驅動方法採用的是黑盒測試,爲了保證測試的穩定性,被測代碼接口的穩定性是非常重要的。否則,變化的成本就會急劇的上升。所以,自動化測試將會要求你的設計依賴於接口,而不是具體的類。這種設計目前被公認是較好的設計思路。

MO技術

這裏不會介紹MO(Mock Object,僞對象)技術,DW專區中已經有這方面的討論文章了。這裏的重點是討論如何正確的使用MO技術。用過MO的人都有這樣的疑惑,MO技術太麻煩了,編寫大量的僞對象僅僅是爲了配合被測試對象的測試工作,未免有些小題大做了。

實際上,MO技術最大的好處並不僅僅是測試本身,MO技術將推動設計向着針對接口,可抽換的方向發展。因爲MO技術需要做到僞對象和實際對象之間能夠平滑的切換,這個能力對於軟件架構來說是非常重要的,它充分表現了架構可擴展性和抗變性。

當然,在實際中,MO技術確實需要增加大量的代碼。有一些工具能夠簡化你的工作,例如EasyMock等一些工具。

測試覆蓋率



上面的兩句話是Clover( http://www.thecortex.net/clover/index.html)的廣告詞,扣除廣告的意味,這句話說得還是非常有道理的。我們的引言中就說明,測試沒有辦法證明錯誤不存在。所以,對測試進行分析是有必要的,而測試覆蓋率就是最重要的一個指標。

測試覆蓋率分析有兩種方法:代碼覆蓋率和分支條件覆蓋率。對測試進行覆蓋率分析是軟件度量的重要方法,也是測試的組成部分,不少的開源項目已經將其納入到了日構建的過程中。


測試的成本

雖然之前我們討論了大量激動人心的思路和技術。你可能會熱血澎湃的打算在組織中實施測試驅動方法。但是我不得不向你潑冷水了。測試驅動方法的引入不是簡單的過程,對一些企業來說,甚至相當難以做到。這是因爲以下這幾個原因:

工作量的估算方式需要改變。在測試驅動方法中,一個開發人員除了需要編寫實現代碼,還需要編寫測試代碼,這將會使得工作量上升,此外,爲了自動化測試而對設計的改進還將會需要一定的時間。所以,要求開發人員學習測試驅動方法沒有任何的意義,關鍵是需要爲他們留出足夠的時間。

項目進度。由於工作量的上升和新知識的使用,項目進度會迅速下落,然後隨着開發人員熟練程度的提升和自動化測試的優勢纔會慢慢回升,如果實施成功,最終的項目速度將會超出實施前,這是完全有可能的。

人員的主動性和勇氣。根據我們的經驗,不少的組織和開發人員都能夠認識到測試驅動的好處,但是往往由於現實環境的原因,導致測試驅動方法的實施無以爲繼。組織由於項目的時間壓力,導致其不敢對測試驅動方法進行推廣,往往是淺嘗則止。個人由於缺乏足夠的耐心和時間,導致其不願和不敢對設計進行重構,而這恰恰是測試驅動的前提。

測試驅動方法的應用沒有那麼簡單,一個組織如果沒有足夠的勇氣是很難做到的。所以,爲什麼一開始我們就強調,本文的讀者是那些應用較負責,對質量非常敏感的項目的相關人員,其中的一個原因就在於此。


建立測試文化

測試驅動方法不是一個簡單的方法論,它也不會和任何的方法論進行競爭。事實上,無論你的組織採用何種方法或過程,都可以從測試驅動中獲利。因爲它強調的是質量文化。

把測試看作一項核心工作,測試同樣需要重構,以及必須的文檔。

固定測試的目錄組織和包組織。例如,一種較好的組織測試的方法是採用和源代碼同樣的包名,但處於完全不同的目錄中。

使測試成爲日創建的核心步驟。

測試是所有人的事情,而不僅是QA的事。


進一步瞭解

從Junit的網站( http://www.junit.org)上,你可以瞭解到Junit的大量信息,包括介紹性的文章,Junit的擴展等。

Test-Driven Development Series Part 1 - Overview ( http://www.theserverside.com/articles/article.jsp?l=TestDrivenDevelopmentPart1)是一組介紹測試驅動開發的文章,和本文不同,它更側重於實際代碼的編寫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章