在過去的幾年中,單元測試逐漸成為我編寫軟件的核心內容,在這里要感謝一種叫做極端編程-XP(注1)(見“資源”一節(jié))的簡便程序設計方法。這種方法要求我為新加入的每個函數(shù)都編寫單元測試,并且維護這些測試。沒有通過單元測試,我就不能將任何一個的代碼加到模塊中。在代碼基數(shù)增長的同時,這些測試允許開發(fā)者有依據(jù)地將改變集成起來。起初,我認為這些單元測試就足以應付全局,沒有必要涉及到功能測試。噢,又錯了。功能測試和單元測試完全不同的兩者。我花費了很長的時間才理解到兩者的區(qū)別,以及如何將它們結合起來,用以改進開發(fā)進程。
本文探討了單元測試和功能測試之間的差別,同時介紹在你的日常開發(fā)的過程中如何來利用它測試和開發(fā)過程作為一個開發(fā)人員,測試如此之重要,以至于你甚至應該花費幾乎所有的時間來完成它。它不僅需要只被劃分為開發(fā)過程中的某個特定階段。顯然,它不該是在你把系統(tǒng)交付給客戶之前完成的最后一項任務。然而,你又如何得知它在何時結束呢?或是你如何得知是否因為修改一個微小的bug而破壞了系統(tǒng)的主要功能呢?或是系統(tǒng)可能會演化成超乎現(xiàn)在想象的模樣?測試,單元的和功能的都應該是開發(fā)的過程中的一部分。
單元測試應成為你編寫代碼的核心環(huán)節(jié),尤其當你在從事一個項目時,緊張的時間約束你的開發(fā)進度,你也很想讓它是在可控的有序下進行。我希望測試也是在你編寫代碼之前編寫測試時的重要內容。
一套適用的單元測試應具備以下功能:
說明可能的最佳適用設計
提供類文檔的最佳格式
判斷一個類何時完成
增強開發(fā)人員對代碼的信心
是快速重構的基礎
在系統(tǒng)中自然要包含單元測試所需的設計文檔。重新閱讀它,你會發(fā)現(xiàn)這是軟件開發(fā)進程中的圣杯,文檔跟隨系統(tǒng)的變化而逐步演化。為每一個類提供完備的文檔比起為它提供一系列的使用框架,或是一系列可控的輸入要好得多。這樣,設計文檔就會因為單元測試的逐步通過而隨時更新。
你應該在你編寫代碼之前完成編寫測試的工序。這樣做會為測試所涉及的類提供設計方案,并促使你關注代碼中更小的程序模塊。這種練習也會使設計方案變得更加簡單。你不能試圖去了解將來的情形,去實現(xiàn)不必要的功能。編寫測試工作也會讓你清楚類會在什么時間結束。可以說,當所有的測試通過時,任務也就完成了。
最后,單元測試會提供給你更高級別的依據(jù),這絕對會滿足開發(fā)者的。如果你在改動代碼的同時,進行單元測試,你就會在你破壞的同時立即察覺到事態(tài)的發(fā)生。
功能測試甚至比單元測試更加重要,因為它們說明了你的系統(tǒng)就要預備發(fā)布了。功能測試將把你的工作系統(tǒng)放置于一個可用的狀態(tài)中。
一套適用的功能測試應具備以下功能:
有效地掌握用戶的需求
向項目組成員(包括用戶和開發(fā)者)給出系統(tǒng)面臨這些需求的依據(jù)
功能測試要在有效地情況下掌握用戶的需求。而傳統(tǒng)的開發(fā)者是在使用的過程中發(fā)現(xiàn)需求的。通常,人們贊同使用項目工程并且花費相當?shù)臅r間去重新定制它們。當它們被完成時,它們所得到的僅僅是一堆廢紙。功能測試雷同于自行生效的使用項目的情況。極端程序設計方法(ExtremeProgramming)能夠說明這種概念。XP 的說法就是對未來發(fā)生在用戶和開發(fā)者之間的交流技巧的描述。功能測試也是這種交流的結果。而沒有功能測試,這種說法也不會建立起來的。
功能測試恰好填充了在單元測試和向項目小組提交的代碼依據(jù)之間的空隙。單元測試會漏過許多的bug。它可以給出代碼中你所需的所有有效部分,它也會給你所需的整個系統(tǒng)。功能測試可以使單元測試里漏掉的問題曝光。一系列可維護的,自動化的功能測試也會有漏網的情況,但是它至少比獨立地進行最全面的單元測試要有用得多。
單元測試VS 功能測試
單元測試告訴開發(fā)者代碼使事情正確地被執(zhí)行,而功能測試所說的則是代碼在正確地發(fā)揮功效。
單元測試
單元測試是從開發(fā)者的角度來編寫的。它們確保類的每個特定方法成功執(zhí)行一系列特定的任務。每一個測試都要保證對于給定的一個已知的輸入應該得到所期望的輸出。
編寫一系列可維護、自動化、沒有測試框架的單元測試幾乎是不可能的。在你開始之前,選擇一個項目小組都認可的框架。不斷地應用它,逐漸地喜歡它。在極端編程的介紹網頁上(見資源一節(jié)),有很多適用的單元測試框架。我喜歡用的是Juint 來進行Java 代碼的測試。
功能測試
功能測試則是從用戶的角度來編寫的。這些測試保證系統(tǒng)能夠按照用戶所期望的那樣去運行。很多時候,開發(fā)一個完整的系統(tǒng)更像是建造一座大樓。當然,這種比喻并不是完全地恰當,但我們可以擴展它,來理解單元測試和功能測試之間的區(qū)別。
單元測試類似于一個建筑檢查員對房屋的建設現(xiàn)場進行檢查。他注重的是房屋內部不同的系統(tǒng),地基,架構設計,電氣化,垂直的線條等等。他檢查房屋的某個部分,以確保它在安全狀態(tài)下,正確無誤地工作,即是說,直接針對房屋的代碼。功能測試在這個劇本里類似于房屋的主人在檢查同樣的建設場地。他所期望的是房屋的內部系統(tǒng)正常地運轉,并且房屋檢查員執(zhí)行了他的任務。房屋的主人看重的是生活在這樣的房屋中會是什么樣子。他關注這間房屋的外貌,不同的房間有合適的空間,房屋適用于家庭的需要,窗戶恰好位于最佳采光的位置。房屋的主人運行的是對房屋的功能測試,他站在用戶的角度上。房屋檢查員運行的是單元測試,他是站在建設者的角度上。
象單元測試一樣,編寫一系列可維護、自動化、沒有測試框架的功能測試幾乎是不可能的。Junit在單元測試方面做得很好;然而,它在試圖編寫功能測試時就顯得比較松散。Junit 不等同于功能測試。現(xiàn)在已經有滿足這個功能的產品問世了,但是我還沒有看到它們被應用于開發(fā)產品過程里。如果你不能找到一個測試框架的話,就只好自己創(chuàng)建一個了。無論我們在建立一個項目時多么聰明,建立的系統(tǒng)多么靈活,如果我們的產品不能用,我們就是在浪費時間。結論是,功能測試是開發(fā)進程中最重要的一部分。
因為兩種類型的測試都是必要的,你會需要編寫它們的指南。
如何編寫單元測試
在你開始編寫單元測試很容易被激動的情緒感染。最簡單的起步方式就是為新的代碼創(chuàng)建單元測試。為已經存在的代碼創(chuàng)建單元測試是一種比較有難度的開始方式,但是也是可行的。)從新的代碼開始,習慣了這樣的步驟后,還要堅持重新閱讀現(xiàn)有代碼,并為它們創(chuàng)建一套測試程序。
就像前面提到過的一樣,你應該在你編寫要測試的代碼之前編寫單元測試。如何做到為還不存在的事物編寫測試呢?好問題!掌握這個能力需要90%的智力和10%技巧。我的意思是你只需假裝是在為已有的類編寫測試。接下來,進行編寫的工作。最初,你將出現(xiàn)很多語法錯誤,但是let it be,不要理會它。緊接著進行單元測試,修改語法錯誤(即是說,只用你自己定義的測試接口來實現(xiàn)類),再一次進行測試。重復這個過程,每一次都寫下充足的代碼去修改錯誤,進行測試直到它們通過為止。當所有的單元測試都通過時,代碼才算真正地完成了。
一般地說,你的類應具有開放的單元測試方式。然而,帶有直截了當?shù)墓δ苄缘姆椒ū热缯f,Java 語言里的Getting 和Setting 讀寫方法,就不需要單元測試,除非它們是以“特殊”的方式進行的。接下來的指導就是,當你感到需要對代碼中的某些特性添加注釋時,同時要編寫出單元測試。如果你同很多的程序員一樣,厭惡為代碼寫注釋,單元測試就是將你的代碼的特性文檔化的一種好方法。
將單元測試同被測試的相關的類打包在一起。(這種組織的方式允許每一個單元測試都能夠直接訪問類中被打包和保護的方法和參數(shù))。要避免在單元測試中用到域對象(domain object)。域對象就是對于一個應用程序特定的對象。
例如,電子表格應用程序有個工作簿對象,它就是一個域對象。如果你的一個類已經知道了域對象,在你的測試中用到這些對象是很好的。但是如果你的類沒有涉及到這些對象,就不要在測試里讓它們同類糾纏不清了。不這樣做的話,就會產生打包的代碼被重用。經常是為一個項目創(chuàng)建的類也可以應用于其他的項目,這樣可能會出現(xiàn)直接重用這些類的情況。但是如果針對這些類的測試也用于另外的項目對象,讓測試生效會很費時,通常測試不是被拋棄掉就是被重新編寫。
以上的一些技巧會讓你從中受益,但最重要的是如果你不實際地去做,就永遠不會對單元測試有全面、深入的理解。更早地運行測試,并且在整個過程中都在代碼中給出全面的依據(jù)。當項目進展時,你會隨時添加更多的特性。運行測試就會提醒你,實現(xiàn)剛添加的特性會不會破壞已有的東西。
在你已經掌握編寫單元測試的技巧之后,你需要重新閱讀已存在的代碼。的確,為它們編寫代碼可能會是一場挑戰(zhàn)。但是千萬不要為了測試的目的而測試。可以說,編寫測試是一件緊跟時效的事情,尤其是當你發(fā)現(xiàn)要修改一個沒有好的測試程序的類時,那就是添加測試的恰當時機。和平常一樣,單元測試應該具備類每個方法的特性。實現(xiàn)測試的一個最簡單的方法就是,測試的同時一定要注意代碼的注釋。在單元測試中,不能放過任何一個注釋,在描述測試方法的開始就要為單元測試添加大量的注釋中。
如何編寫功能測試
盡管功能測試是如此重要,它也有個開發(fā)過程里丑陋的繼生子的壞名聲。在大多數(shù)的項目里,是由一個獨立的工作組來完成功能測試的工作。通常需要一群人在系統(tǒng)中的相互協(xié)助才能保證工序的正確運行。這種通常的看法和隊伍的組建的做法,都是非常愚蠢的。
功能測試同單元測試相類似。一旦要編寫有用戶涉入的產品的代碼(例如,對話框)時,就要編寫測試,但是一定要在實際編寫代碼之前做。一旦你開始了一項新任務,就要在功能測試的框架里清楚地描述這個任務的內容。你加入的新代碼的同時進行單元測試,開發(fā)工作就向前持續(xù)進行。
當所有的單元測試都進行通過后,再進行最初的功能測試來判斷項目是否可以通過,或是需要修改。理想的狀況下,功能測試小組的概念應該不存在的。開發(fā)者應該同用戶一同編寫功能測試。系統(tǒng)通過了一系列的單元測試后,負責進行功能測試的小組成員就要改變初試測試的參數(shù),再進行系統(tǒng)的功能測試。
單元測試和功能測試之間的界線
一般情況下,很難劃清在單元測試和功能測試之間的界限。說實話,一直以來,我就不知道這個界線應該定在哪里。當編寫單元測試時,我用以下幾個方法來判定單元測試是不是已經變成了功能測試:
如果單元測試超越了類之間的界限,它可能變成了功能測試
如果單元測試變得非常的復雜,它可能變成了功能測試
如果單元測試變得很脆弱(即是說,它已經成為一個測試,但是卻因為要迎合不同用戶需求的改變而被動地變化),它可能變成了功能測試
如果單元測試比需要測試的代碼還要難于編寫,它可能變成了功能測試
注意“它可能變成了功能測試”的說法,在這里沒有嚴格的標準。在單元測試和功能測試之間是有界線的,但是你必須自己判定它在哪里。單元測試進行地順利,特定的測試逾越兩者界線的過渡就越明顯。
結論
單元測試以開發(fā)者的角度來編寫,并注重被測試類的特性。當編寫單元測試時,利用以下幾條指導:
在類代碼進行測試之前編寫單元測試
在單元測試里掌握代碼的注釋
測試所有執(zhí)行特定功能的公用程序(即是說,和Java 語言中的Getting 和Setting 讀寫方法不同的方法。除非它們是通過一種特殊的方式來完成Getting 和Setting 功能的。)
將所有的測試項目同被測試的類打包在一起,并且分配它們對在模塊包內的和被保護成員
的訪問權限
在單元測試中避免使用某些特定的對象
功能測試也需要從用戶的角度出發(fā)來編寫,并且注重用戶所感興趣的系統(tǒng)功能。選擇一個適當?shù)墓δ軠y試框架,或是開發(fā)出一種,并利用這些功能測試來制定用戶們想要的東西。通過這種方式,功能測試的人員可以獲得一個自動的工具,并且對使用工具的習慣有了一個好的起點。
將單元測試和功能測試作為開發(fā)進程的核心內容。這樣做,你就會確定系統(tǒng)在正常運轉。如果沒有,你恐怕不能保證系統(tǒng)是正常工作的。測試可能不是一件好玩的事情,但是從事單元測試和功能測試會使開發(fā)過程里含有更多的樂趣。
資源
“利用Ant 和JUnit 改進開發(fā)過程”(開發(fā)工作,2000 年12 月)揭示了單元測試的益處,尤其是應用了Ant 和Junit 之后。
開始了解極端編程的方法
從極端編程的網頁上下載各種單元測試的框架
posted on 2006-11-26 15:03
matthew 閱讀(275)
評論(0) 編輯 收藏 所屬分類:
軟件測試技術