| 2005 年 4 月 01 日 本文來(lái)自于 Rational Edge:本文描述了優(yōu)先測試的程序設計實(shí)踐,或稱(chēng)為 TFP,TFP 提出了在您真正書(shū)寫(xiě)代碼之前為代碼生成測試。Pollice 利用擴展的例子對實(shí)踐進(jìn)行了說(shuō)明,并為軟件開(kāi)發(fā)人員及他們的團隊概括出 TFP 的益處。 ![]() 在開(kāi)始對此實(shí)踐進(jìn)行檢驗之前,我們需要一些關(guān)于什么時(shí)候使用它的背景知識。要理解這一點(diǎn),讓我們了解一下項目范圍和流程靈活性之間的關(guān)系。在這幾年與客戶(hù)打交道的工作中,我注意到,隨著(zhù)項目范圍的擴展,對統一的形式化流程的需求也成比例地增加(參見(jiàn)圖 1)。大多數有經(jīng)驗的開(kāi)發(fā)人員積累了了大量的實(shí)踐和技術(shù),并可以根據工作內容有選擇地應用他們。當他們獨自完成任務(wù)時(shí),他們可以以自己的方式工作。但是,如果他們在團隊中——特別是在企業(yè)級——項目經(jīng)理必須設立更高級別的流程以保持整個(gè)團隊工作的一致性。通常,該流程必須是相當地穩固的,因為背離它將影響到許多人。 ![]()
例如,讓我們假設您是軟件架構師,總是按照軟件需求規范(Software Requirement Specification,SRS)來(lái)將項目功能需求描述成一列不相干的“應該有”的條目。但之后您參加了一個(gè)關(guān)于用例的專(zhuān)題討論會(huì ),并認識到用例更加正確地描述了為什么系統將對涉眾具有意義。盡管您毫無(wú)疑問(wèn)地會(huì )采用更好的技術(shù),但是切換到當前項目的用例會(huì )導致混亂——除非您能夠確保團隊中的每個(gè)人也進(jìn)行了切換。 當然,如果您在處理一個(gè)大項目中的小項目,您可能會(huì )采用不同的技術(shù)來(lái)優(yōu)化您的工作,只要這些技術(shù)不會(huì )干擾更廣泛的組織實(shí)踐。例如,如果大項目要求為用戶(hù)界面使用專(zhuān)門(mén)的 2-D 和 3-D 圖形,為繪制這些圖形而實(shí)現類(lèi)庫的子項目可能采取特殊方法來(lái)描述需求。 這不會(huì )對其他流程的活動(dòng)產(chǎn)生負面影響。另一個(gè)適于模型驅動(dòng)開(kāi)發(fā)的子項目可能使用 UML 模型來(lái)生成子項目代碼的重要部分——沒(méi)有妨礙整體項目代碼的生成規程。
優(yōu)先測試的程序設計:開(kāi)發(fā)人員的個(gè)人實(shí)踐 當他們在小項目或子項目中用 他們的方式靈活地工作時(shí),一些開(kāi)發(fā)人員選擇應用極限編程(Extreme Programming,XP) 1 方法的理論。本文不會(huì )討論對項目“進(jìn)行 XP”的意思,本文將著(zhù)眼于 TFP,即與此方法論(XP)相關(guān)的實(shí)踐,我認為 TFP 幫助許多軟件專(zhuān)業(yè)人員創(chuàng )造更好的工作。盡管您依照規范來(lái)應用并完善 TFP,我發(fā)覺(jué) TFP 很容易理解并且很適合我的工作方式。它還很容易向我的學(xué)生示范,并且我已經(jīng)教授了學(xué)生如何在學(xué)期項目中利用它。 下面的說(shuō)明是依據 我如何應用 TFP 的經(jīng)歷闡述的。盡管這些不是詳盡的說(shuō)明,但它也許會(huì )提供足夠的信息激起您的欲望并鼓勵您學(xué)習更多的關(guān)于 TFP 的內容。為了達到這個(gè)目的,我在 參考資料部分提供了許多資源。 TFP(也被稱(chēng)為測試驅動(dòng)設計或測試驅動(dòng)開(kāi)發(fā))實(shí)際上是用于實(shí)現 XP 所描述的單元測試實(shí)踐的流程,該流程簡(jiǎn)單地描述為: 在您寫(xiě)代碼之前,確保您擁有失敗了的測試。換句話(huà)說(shuō),寫(xiě)一個(gè)將您要撰寫(xiě)的代碼的測試。 Ron Jeffries 將流程描述如下:
這似乎非常簡(jiǎn)單,但您需要滿(mǎn)足兩個(gè)需求才能夠成功地采用 TFP。首先,您必須了解如何書(shū)寫(xiě)一個(gè)好的測試。第二,您必須要求自己在編碼之前書(shū)寫(xiě)測試。這可能與您在培訓時(shí)所學(xué)到的背道而馳。 在確定要測試的內容后,Jeffries 說(shuō),“選擇您能想到的所增加的最少的新功能?!边@是個(gè)好建議。大多數初學(xué)者都試圖在每個(gè)單元測試中填入太多的要素。然而,最少的增量也不總是最好的選擇。當您對書(shū)寫(xiě)測試很有經(jīng)驗時(shí),您會(huì )發(fā)現對您來(lái)說(shuō)什么是最舒適最有效的。 對于我來(lái)說(shuō),一個(gè)好的測試取決于一樣東西。然而,如何定義“一樣東西”要根據我想編寫(xiě)代碼的類(lèi)型而不同。這和設計方法和類(lèi)的原則類(lèi)似。您想要整個(gè)設計具有高凝聚力,測試也應當具有結合性。 要實(shí)施 TFP,您需要有自動(dòng)化的工具?;旧?,TFP 的循環(huán)是測試/編碼/調試/重新分解/重復。如果沒(méi)有工具來(lái)支持這些活動(dòng)間的快速轉移,您很快就會(huì )受挫并放棄了。 支持 TFP 的典型工具是良好的集成開(kāi)發(fā)環(huán)境(integrated development environments IDEs)和單元測試框架。我使用帶有 JUnit 插件的 Eclipse IDE 來(lái)進(jìn)行所有的 Java 開(kāi)發(fā)。 JUnit 是由 Kent Beck 和 Erich Gamma 為測試 Java 程序而設計的單元測試框架。 3 JUnit 的變化版本支持對其他語(yǔ)言程序的測試,例如用于 C++ 語(yǔ)言的 cppUnit。在本文下個(gè)部分,我將用 JUnit 來(lái)創(chuàng )建實(shí)例。 用于軟件測試的類(lèi)和程序經(jīng)常使用一個(gè)示例程序,程序接收三個(gè)數值,然后將這三個(gè)數值作為邊長(cháng)來(lái)測試三角形的類(lèi)型。如果您用三角形的角度進(jìn)行測試,那么可能的類(lèi)型是直角、銳角、鈍角或等角三角形。如果您通過(guò)三角形的邊進(jìn)行測試,可能的類(lèi)型是等邊、等腰或不規則三角形。要示范如何使用 TFP,我們將創(chuàng )建此示例程序,并包含名為 ![]()
在此處我必須做出幾個(gè)決定。首先,我需要決定用作參數的數據類(lèi)型。我選擇最一般的類(lèi)型,雙精度實(shí)數。我還要決定如何表示三角形類(lèi)型。經(jīng)過(guò)考慮,我創(chuàng )建了名為 由于不存在 ![]()
現在我準備書(shū)寫(xiě)第一個(gè)測試。Eclipse 可以為 很多測試用例都可用于測試 首先應該測試什么呢?如我上面所提到的,我們可以將三角形的類(lèi)型按照角度或邊來(lái)分類(lèi),在某些情況下,我們提供的三個(gè)數值也許不能組成三角形。先選擇哪種可能的情況無(wú)關(guān)緊要,因此我選擇直角三角形。 創(chuàng )建名為 ![]()
勾股定理(Pythagorean Theorem)告訴我們邊長(cháng)為 3、4 和 5 的三角形是直角三角形,因此可以使用邊長(cháng) 3、4 和 5 來(lái)簡(jiǎn)單地測試。 5 甚至在將 當實(shí)施 TFP 時(shí),只添加了足夠讓測試通過(guò)的代碼。在這種情況下,我需要真實(shí)值,要做到這些的最簡(jiǎn)單的方法是加入代碼示例 4 中的代碼。 ![]()
您可能懷疑是否應該為 現在執行我的第一個(gè)測試。在 Eclipse 中執行測試非常簡(jiǎn)單。選擇 ![]()
現在我知道問(wèn)題所在了。 ![]()
接下來(lái)要做什么?考慮用非直角三角形?如果輸入不同的數,是否還是直角三角形?在寫(xiě)好的測試方法中添加一個(gè)關(guān)于此問(wèn)題的測試,如代碼示例 5 所示。 ![]()
現在,當運行測試時(shí),在剛添加的部分出現聲明錯誤。原因很簡(jiǎn)單?,F在 ![]()
現在,我自然要為 ![]()
將添加的直角三角形的屬性設為私有變量,并增加設置方法。 ![]()
此時(shí) JUnit 中再次出現綠色條,因此我可以繼續添加新功能。還應該繼續測試其他類(lèi)型的三角形嗎?也許,但在某種程度上,我必須處理實(shí)數運算在計算機上是不精確的事實(shí)。我知道這要憑經(jīng)驗。所以此時(shí)我想看看到現在為止我所書(shū)寫(xiě)的代碼能否正確處理實(shí)數。(我肯定代碼不能正確處理,但需要用測試來(lái)確定)將代碼示例 9 中的測試添加到直角三角形測試中。毫無(wú)疑問(wèn),JUnit 顯示出紅色條:測試失敗。 ![]()
現在我要做出一個(gè)設計的決定。如何處理精度問(wèn)題呢?我所能想到的所有解決方案都需要重新分解,返工。經(jīng)驗表明一些返工是不可避免的。至少我現在有一組要求代碼必須能夠通過(guò)的測試,到現在為止,我還沒(méi)有進(jìn)入需要大量返工的編碼階段。 6 需要一個(gè)delta——可接受的算術(shù)錯誤的量。我想讓客戶(hù)程序測試保持原樣,因此添加了默認的 delta,刪掉了直角三角形測試的返回語(yǔ)句,并加入了代碼示例 10 中所示的代碼到 ![]()
然后,我繼續添加測試,每次通過(guò)添加新代碼、變更現有代碼及重新分解的方式來(lái)修改代碼。最終,實(shí)現了一個(gè)完整的類(lèi),我對之很有信心。我還有可以隨時(shí)運行的測試。將來(lái)我如果改變代碼,或其他人改變代碼,這些測試會(huì )告訴我們是否我們破壞了現有的功能。 在這里,我將停止向您描述所有細節。在繼續之前,看看表 1,顯示了我所寫(xiě)的測試并按照我所寫(xiě)的順序排列。還要了解一下在代碼改進(jìn)過(guò)程中我所必須做出的決定。
我用了大約 100 行 Java 代碼和 70 行測試代碼實(shí)現了
采用 TFP 會(huì )有許多好處,我已經(jīng)感受過(guò)一些好處了。您的工作風(fēng)格和環(huán)境將決定您從實(shí)踐中得到多少價(jià)值。 TFP 最明顯的好處就是,它為您寫(xiě)的代碼提供測試。讓我吃驚的是,今天有多少程序員在編寫(xiě)代碼時(shí)不去考慮測試。也許一些組織沒(méi)有清楚地表述出他們對最終代碼的質(zhì)量期望值,許多組織運用了將整個(gè)測試負擔加到質(zhì)量保證組上的流程。 事實(shí)上, 每個(gè)人都要對質(zhì)量負責——不論您如何定義。要求程序員在將代碼加入項目其他部分中時(shí)要進(jìn)行單元測試,這種想法是合理的。如果程序員使用 TFP,他們就不得不對代碼進(jìn)行測試。沒(méi)有什么辦法可以省去測試。剛剛學(xué)習如何測試代碼的學(xué)生經(jīng)常認為 TFP 不一定會(huì )生成好的測試。我告訴他們,必須判斷是否“壞的”測試會(huì )比根本不測試要好。也許我們將在未來(lái)的專(zhuān)欄中探究此問(wèn)題。 三角形測試實(shí)例一共有七個(gè)測試方法,但有二十三個(gè)截然不同的測試。這還不是全集。我還可以加入更多,但我相信我所開(kāi)發(fā)的測試非常健壯。如果沒(méi)有書(shū)寫(xiě)任何測試,我不會(huì )有這樣的信心。 您可以通過(guò)許多方法來(lái)測量代碼覆蓋率:通過(guò)評估代碼行或語(yǔ)句的覆蓋面、條件覆蓋面、分支覆蓋面等等。當采用 TFP 時(shí),可以實(shí)現 100% 的代碼覆蓋率。盡管我的那些使用了 TFP 的特殊方法沒(méi)有提供 100% 的覆蓋率(參見(jiàn)下面的多少量足夠?部分),但該方法為可能包含缺陷的代碼段提供了可以接受的覆蓋水平。 測試人員贊同,大約 80% 的代碼覆蓋率是可以接受的水平。試圖實(shí)現完全承保常常是浪費時(shí)間,如果您花上幾個(gè)小時(shí)去測試錯誤情況和異常情況,您的受益會(huì )減少。 理論上說(shuō),如果為滿(mǎn)足現有的測試而撰寫(xiě)代碼,那么您肯定能夠實(shí)現對您所寫(xiě)的 每行代碼的覆蓋。然而,這不太實(shí)際。我沒(méi)有見(jiàn)過(guò)任何實(shí)驗研究證實(shí)有 100% 的覆蓋率。 重點(diǎn)是,采用 TFP 可以確保您在單元測試中實(shí)現相當大的代碼覆蓋率——可能遠遠超過(guò)我今天所達到的。 設計在發(fā)展,我通過(guò)經(jīng)驗了解到這一點(diǎn)。當業(yè)務(wù)環(huán)境改變或涉眾提出新的需求時(shí),我們需要一些方法來(lái)修改現有代碼。這就是軟件為什么要軟且靈活的原因。 當使用 TFP 來(lái)驅動(dòng)您的設計時(shí),實(shí)際上您已經(jīng)采用了測試驅動(dòng)設計(test-driven design,TDD)的方法來(lái)構建具有更加簡(jiǎn)單更加靈活的體系結構的軟件。 在上面的三角形測試實(shí)例中,我按照自己書(shū)寫(xiě)的測試修改了設計。在測試的響應中添加了常量 當實(shí)現對等邊三角形和等腰三角形的測試時(shí),我認識到我只用了 delta 值來(lái)用于直角三角形的計算。也許我會(huì )加入“誤差因素”,和 delta 一起來(lái)確定是等腰三角形還是等邊三角形。但是,由于我現在不需要,所以我可以將它推遲到另外一天——我也許真的需要它的時(shí)候。 TFP 或 TDD 是用來(lái)補充其他設計工具和技術(shù)的實(shí)現。隨著(zhù)經(jīng)驗的豐富,我將會(huì )找到更多的方法來(lái)應用它并能更好的理解它所適用的時(shí)間和地點(diǎn)。 當您使用完 TFP 后,您所創(chuàng )建的測試就代表了一組可執行的規范。只要您保持測試和代碼的同步(這是實(shí)踐的關(guān)鍵點(diǎn)),如果有程序員想知道系統能做什么,他或她可以參看測試。 測試不是您所需的唯一的規范。讓客戶(hù)通過(guò)測試來(lái)判斷您的規范是否正確是不合理的。作為軟件工程師,我們知道存在可以用不同方式表示的許多類(lèi)型的需求。認為一種類(lèi)型的需求可以滿(mǎn)足每個(gè)涉眾,或者認為可以很容易地使代碼和需求保持同步是很愚蠢的。然而,在此領(lǐng)域,TFP 真的提供了支持。 Mike Ciaraldi 教授,是我在伍斯特工業(yè)學(xué)院的一個(gè)同事,他說(shuō)要“小規?!钡兀ǘ谴笠幠5兀┖饬宽椖窟M(jìn)展。我們致力于非常小的進(jìn)展,使它正確,然后繼續下一個(gè)進(jìn)展。最終,所有的小進(jìn)展匯集成大的進(jìn)展。 如果想使用測試來(lái)限制進(jìn)度,那么我們可以通過(guò)撰寫(xiě)優(yōu)先測試立刻滿(mǎn)足要求,并實(shí)現代碼使之得以工作。我發(fā)現采用 TFP 可以防止出現沒(méi)有內容的分析,以及由于我們不了解所有的限制和所面臨的所有可能出現的問(wèn)題而害怕去撰寫(xiě)代碼的情形。 像中國的古老諺語(yǔ)所說(shuō)的,“千里之行始于足下?!?TFP 幫助我們邁出第一步,然后下一步,等等。 TFP 是很容易理解的實(shí)踐。在本學(xué)期,我用了一個(gè)小時(shí)的課對它進(jìn)行介紹,提供參考資料,并帶著(zhù)學(xué)生完成了一個(gè)示例,例如三角形測試程序。在這一個(gè)小時(shí)的最后,學(xué)生們都走出去準備試試運氣。 許多學(xué)生很快就掌握了 TFP 并報告說(shuō) TFP 改變了他們創(chuàng )建程序的方式。其他人不適應 TFP,我沒(méi)有強迫他們采用此實(shí)踐。目前,我希望他們去體驗很多軟件開(kāi)發(fā)實(shí)踐并使用適合他們風(fēng)格的實(shí)踐。但是,如果在他們的職業(yè)生涯中需要 TFP 時(shí),至少他們知道如何使用。如果您是項目經(jīng)理,您可能希望使用類(lèi)似的方法,并花費一些時(shí)間培訓您的團隊使用 TFP。
大多數想要采用 TFP 的程序員會(huì )問(wèn):“多少就足夠了?有必要為每行代碼和每個(gè)方法書(shū)寫(xiě)測試嗎?” 對此有一些不同的看法。我會(huì )測試我所實(shí)現的大部分方法。但是,我也不厭煩為 IDE 生成的方法書(shū)寫(xiě)測試。 進(jìn)行測試的多少取決于項目目標和可用的時(shí)間。盡管為每個(gè)您撰寫(xiě)的方法創(chuàng )建多種測試是非常不錯的,但您必須決定您實(shí)際能測試多少,結果是否能體現出您所投入的時(shí)間。
TFP 是實(shí)用的實(shí)踐,不論您是否將 TFP 作為設計工具來(lái)使用。它提供廣泛的覆蓋面,是相當簡(jiǎn)單的可防止編碼災難的方法。如果認為要編寫(xiě)的代碼很簡(jiǎn)單,我們會(huì )抄近路。但是如果您見(jiàn)過(guò)由于一行代碼的變更而導致系統的失敗,您就會(huì )理解多進(jìn)行測試的必要了。 您可以在幾乎任何項目環(huán)境中應用 TFP,不論組織或項目流程中是否指出要使用它。當您的隊友看到您代碼的質(zhì)量后,他們也許會(huì )問(wèn)您如何進(jìn)行改進(jìn)。您可以簡(jiǎn)單地分享您的“秘密”,并使之成為團隊流程中的一個(gè)部分。如果沒(méi)有成功地使之加入到流程中,您仍舊可以為您所撰寫(xiě)的測試和代碼自豪。
單擊此處,下載示例程序Triangle Tester 。
1對 XP 不熟悉的讀者可以參考http://c2.com/cgi/wiki?ExtremeProgrammingRoadmap。 2參見(jiàn)http://c2.com/cgi/wiki?CodeUnitTestFirst。 3要了解更多關(guān)于 JUnit 的信息,請訪(fǎng)問(wèn)http://www.junit.org。閱讀該站點(diǎn)的 Test Infected 論文,獲得對框架和流程的總覽。同時(shí)在本文底部的參考資料中列出了一些講述如何在 TFP 的環(huán)境下使用 JUnit 的書(shū)籍和論文。 4我們不會(huì )展示完整的程序,只展示一個(gè)實(shí)現功能的類(lèi)。用 Eclipse 和 JUnit 來(lái)對該類(lèi)進(jìn)行測試是非常簡(jiǎn)單的。您可以通過(guò)點(diǎn)擊文章底部的鏈接,下載該類(lèi)的所有代碼。 5 勾股定理(Pythagorean Theorem)指 32 + 42 = 9 + 16 = 25 = 52。 6很明顯,如果等到開(kāi)發(fā)周期的晚一些的階段再添加測試,我就會(huì )做更多的返工。這是經(jīng)驗之談。
| ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
聯(lián)系客服