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