• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            不倦的候鳥成長日記

            ——候鳥,候補的菜鳥也
            隨筆 - 6, 文章 - 0, 評論 - 5, 引用 - 0
            數據加載中……

            [轉]測試驅動開發全功略

            From: Brian Sun @ 爬樹的泡泡[http://www.blogjava.net/briansun]

            {關鍵字}

            測試驅動開發/Test Driven Development/TDD
            測試用例/TestCase/TC
            設計/Design
            重構/Refactoring

            {TDD的目標}

            Clean Code That Works

            這句話的含義是,事實上我們只做兩件事情:讓代碼奏效(Work)和讓代碼潔凈(Clean),前者是把事情做對,后者是把事情做好。想想看,其實我們平時所做的所有工作,除去無用的工作和錯誤的工作以外,真正正確的工作,并且是真正有意義的工作,其實也就只有兩大類:增加功能和提升設計,而TDD 正是在這個原則上產生的。如果您的工作并非我們想象的這樣,(這意味著您還存在第三類正確有意義的工作,或者您所要做的根本和我們在說的是兩回事),那么這告訴我們您并不需要TDD,或者不適用TDD。而如果我們偶然猜對(這對于我來說是偶然,而對于Kent Beck和Martin Fowler這樣的大師來說則是辛勤工作的成果),那么恭喜您,TDD有可能成為您顯著提升工作效率的一件法寶。請不要將信將疑,若即若離,因為任何一項新的技術——只要是從根本上改變人的行為方式的技術——就必然使得相信它的人越來越相信,不信的人越來越不信。這就好比學游泳,唯一能學會游泳的途徑就是親自下去游,除此之外別無他法。這也好比成功學,即使把卡耐基或希爾博士的書倒背如流也不能擁有積極的心態,可當你以積極的心態去成就了一番事業之后,你就再也離不開它了。相信我,TDD也是這樣!想試用TDD的人們,請遵循下面的步驟:

            編寫TestCase --> 實現TestCase --> 重構
            (確定范圍和目標)   (增加功能)   (提升設計)

            [友情提示:敏捷建模中的一個相當重要的實踐被稱為:Prove it With Code,這種想法和TDD不謀而合。]

            {TDD的優點}

              『充滿吸引力的優點』

            1. 完工時完工。表明我可以很清楚的看到自己的這段工作已經結束了,而傳統的方式很難知道什么時候編碼工作結束了。
            2. 全面正確的認識代碼和利用代碼,而傳統的方式沒有這個機會。
            3. 為利用你成果的人提供Sample,無論它是要利用你的源代碼,還是直接重用你提供的組件。
            4. 開發小組間降低了交流成本,提高了相互信賴程度。
            5. 避免了過渡設計。
            6. 系統可以與詳盡的測試集一起發布,從而對程序的將來版本的修改和擴展提供方便。
            7. TDD給了我們自信,讓我們今天的問題今天解決,明天的問題明天解決,今天不能解決明天的問題,因為明天的問題還沒有出現(沒有TestCase),除非有TestCase否則我決不寫任何代碼;明天也不必擔心今天的問題,只要我亮了綠燈。

              『不顯而易見的優點』

            8. 逃避了設計角色。對于一個敏捷的開發小組,每個人都在做設計。
            9. 大部分時間代碼處在高質量狀態,100%的時間里成果是可見的。
            10. 由于可以保證編寫測試和編寫代碼的是相同的程序員,降低了理解代碼所花費的成本。
            11. 為減少文檔和代碼之間存在的細微的差別和由這種差別所引入的Bug作出杰出貢獻。
            12. 在預先設計和緊急設計之間建立一種平衡點,為你區分哪些設計該事先做、哪些設計該迭代時做提供了一個可靠的判斷依據。

              『有爭議的優點』

            13. 事實上提高了開發效率。每一個正在使用TDD并相信TDD的人都會相信這一點,但觀望者則不同,不相信TDD的人甚至堅決反對這一點,這很正常,世界總是這樣。
            14. 發現比傳統測試方式更多的Bug。
            15. 使IDE的調試功能失去意義,或者應該說,避免了令人頭痛的調試和節約了調試的時間。
            16. 總是處在要么編程要么重構的狀態下,不會使人抓狂。(兩頂帽子)
            17. 單元測試非常有趣。
            {TDD的步驟}
            編寫TestCase --> 實現TestCase --> 重構
            (不可運行)   (可運行)   (重構)
            步驟 制品
            (1)快速新增一個測試用例 新的TestCase
            (2)編譯所有代碼,剛剛寫的那個測試很可能編譯不通過 原始的TODO List
            (3)做盡可能少的改動,讓編譯通過 Interface
            (4)運行所有的測試,發現最新的測試不能編譯通過 -(Red Bar)
            (5)做盡可能少的改動,讓測試通過 Implementation
            (6)運行所有的測試,保證每個都能通過 -(Green Bar)
            (7)重構代碼,以消除重復設計 Clean Code That Works

            {FAQ}

            [什么時候重構?]
            如果您在軟件公司工作,就意味著您成天都會和想通過重構改善代碼質量的想法打交道,不僅您如此,您的大部分同事也都如此。可是,究竟什么時候該重構,什么情況下應該重構呢?我相信您和您的同事可能有很多不同的看法,最常見的答案是“該重構時重構”,“寫不下去的時候重構”,和“下一次迭代開始之前重構”,或者干脆就是“最近沒時間,就不重構了,下次有時間的時候重構吧”。正如您已經預見到我想說的——這些想法都是對重構的誤解。重構不是一種構建軟件的工具,不是一種設計軟件的模式,也不是一個軟件開發過程中的環節,正確理解重構的人應該把重構看成一種書寫代碼的方式,或習慣,重構時時刻刻有可能發生。在TDD中,除去編寫測試用例和實現測試用例之外的所有工作都是重構,所以,沒有重構任何設計都不能實現。至于什么時候重構嘛,還要分開看,有三句話是我的經驗:實現測試用例時重構代碼,完成某個特性時重構設計,產品的重構完成后還要記得重構一下測試用例哦。

            [什么時候設計?]
            這個問題比前面一個要難回答的多,實話實說,本人在依照TDD開發軟件的時候也常常被這個問題困擾,總是覺得有些問題應該在寫測試用例之前定下來,而有些問題應該在新增一個一個測試用例的過程中自然出現,水到渠成。所以,我的建議是,設計的時機應該由開發者自己把握,不要受到TDD方式的限制,但是,不需要事先確定的事一定不能事先確定,免得捆住了自己的手腳。

            [什么時候增加新的TestCase?]
            沒事做的時候。通常我們認為,如果你要增加一個新的功能,那么先寫一個不能通過的 TestCase;如果你發現了一個bug,那么先寫一個不能通過的TestCase;如果你現在什么都沒有,從0開始,請先寫一個不能通過的 TestCase。所有的工作都是從一個TestCase開始。此外,還要注意的是,一些大師要求我們每次只允許有一個TestCase亮紅燈,在這個 TestCase沒有Green之前不可以寫別的TestCase,這種要求可以適當考慮,但即使有多個TestCase亮紅燈也不要緊,并未違反TDD 的主要精神。

            [TestCase該怎么寫?]
            測試用例的編寫實際上就是兩個過程:使用尚不存在的代碼和定義這些代碼的執行結果。所以一個 TestCase也就應該包括兩個部分——場景和斷言。第一次寫TestCase的人會有很大的不適應的感覺,因為你之前所寫的所有東西都是在解決問題,現在要你提出問題確實不大習慣,不過不用擔心,你正在做正確的事情,而這個世界上最難的事情也不在于如何解決問題,而在于ask the right question!

            [TDD能幫助我消除Bug嗎?]
            答:不能!千萬不要把“測試”和“除蟲”混為一談!“除蟲”是指程序員通過自己的努力來減少bug的數量(消除bug這樣的字眼我們還是不要講為好^_^),而“測試”是指程序員書寫產品以外的一段代碼來確保產品能有效工作。雖然TDD所編寫的測試用例在一定程度上為尋找bug提供了依據,但事實上,按照TDD的方式進行的軟件開發是不可能通過TDD再找到bug的(想想我們前面說的“完工時完工”),你想啊,當我們的代碼完成的時候,所有的測試用例都亮了綠燈,這時隱藏在代碼中的bug一個都不會露出馬腳來。

            但是,如果要問“測試”和“除蟲”之間有什么聯系,我相信還是有很多話可以講的,比如TDD事實上減少了bug的數量,把查找bug戰役的關注點從全線戰場提升到代碼戰場以上。還有,bug的最可怕之處不在于隱藏之深,而在于滿天遍野。如果你發現了一個用戶很不容易才能發現的bug,那么不一定對工作做出了什么杰出貢獻,但是如果你發現一段代碼中,bug的密度或離散程度過高,那么恭喜你,你應該拋棄并重寫這段代碼了。TDD避免了這種情況,所以將尋找bug的工作降低到了一個新的低度。

            [我該為一個Feature編寫TestCase還是為一個類編寫TestCase?]
            初學者常問的問題。雖然我們從TDD 的說明書上看到應該為一個特性編寫相應的TestCase,但為什么著名的TDD大師所寫的TestCase都是和類/方法一一對應的呢?為了解釋這個問題,我和我的同事們都做了很多試驗,最后我們得到了一個結論,雖然我不知道是否正確,但是如果您沒有答案,可以姑且相信我們。

            我們的研究結果表明,通常在一個特性的開發開始時,我們針對特性編寫測試用例,如果您發現這個特性無法用TestCase表達,那么請將這個特性細分,直至您可以為手上的特性寫出TestCase為止。從這里開始是最安全的,它不會導致任何設計上重大的失誤。但是,隨著您不斷的重構代碼,不斷的重構 TestCase,不斷的依據TDD的思想做下去,最后當產品伴隨測試用例集一起發布的時候,您就會不經意的發現經過重構以后的測試用例很可能是和產品中的類/方法一一對應的。

            [什么時候應該將全部測試都運行一遍?]
            Good Question!大師們要求我們每次重構之后都要完整的運行一遍測試用例。這個要求可以理解,因為重構很可能會改變整個代碼的結構或設計,從而導致不可預見的后果,但是如果我正在開發的是一個ERP怎么辦?運行一遍完整的測試用例可能將花費數個小時,況且現在很多重構都是由工具做到的,這個要求的可行性和前提條件都有所動搖。所以我認為原則上你可以挑幾個你覺得可能受到本次重構影響的TestCase去run,但是如果運行整個測試包只要花費數秒的時間,那么不介意你按大師的要求去做。

            [什么時候改進一個TestCase?]
            增加的測試用例或重構以后的代碼導致了原來的TestCase的失去了效果,變得無意義,甚至可能導致錯誤的結果,這時是改進TestCase的最好時機。但是有時你會發現,這樣做僅僅導致了原來的TestCase在設計上是臃腫的,或者是冗余的,這都不要緊,只要它沒有失效,你仍然不用去改進它。記住,TestCase不是你的產品,它不要好看,也不要怎么太科學,甚至沒有性能要求,它只要能完成它的使命就可以了——這也證明了我們后面所說的“用Ctrl-C/Ctrl-V編寫測試用例”的可行性。

            但是,美國人的想法其實跟我們還是不太一樣,拿托尼巴贊的MindMap來說吧,其實畫MindMap只是為了表現自己的思路,或記憶某些重要的事情,但托尼卻建議大家把MindMap畫成一件藝術品,甚至還有很多藝術家把自己畫的抽象派MindMap拿出來幫助托尼做宣傳。同樣,大師們也要求我們把TestCase寫的跟代碼一樣質量精良,可我想說的是,現在國內有幾個公司能把產品的代碼寫的精良??還是一步一步慢慢來吧。

            [為什么原來通過的測試用例現在不能通過了?]
            這是一個警報,Red Alert!它可能表達了兩層意思——都不是什么好意思——1)你剛剛進行的重構可能失敗了,或存在一些錯誤未被發現,至少重構的結果和原來的代碼不等價了。2)你剛剛增加的TestCase所表達的意思跟前面已經有的TestCase相沖突,也就是說,新增的功能違背了已有的設計,這種情況大部分可能是之前的設計錯了。但無論哪錯了,無論是那層意思,想找到這個問題的根源都比TDD的正常工作要難。

            [我怎么知道那里該有一個方法還是該有一個類?]
            這個問題也是常常出現在我的腦海中,無論你是第一次接觸TDD或者已經成為 TDD專家,這個問題都會纏繞著你不放。不過問題的答案可以參考前面的“什么時候設計”一節,答案不是唯一的。其實多數時候你不必考慮未來,今天只做今天的事,只要有重構工具,從方法到類和從類到方法都很容易。

            [我要寫一個TestCase,可是不知道從哪里開始?]
            從最重要的事開始,what matters most?從腳下開始,從手頭上的工作開始,從眼前的事開始。從一個沒有UI的核心特性開始,從算法開始,或者從最有可能耽誤時間的模塊開始,從一個最嚴重的bug開始。這是TDD主義者和鼠目寸光者的一個共同點,不同點是前者早已成竹在胸。

            [為什么我的測試總是看起來有點愚蠢?]
            哦?是嗎?來,握個手,我的也是!不必擔心這一點,事實上,大師們給的例子也相當愚蠢,比如一個極端的例子是要寫一個兩個int變量相加的方法,大師先斷言2+3=5,再斷言5+5=10,難道這些代碼不是很愚蠢嗎?其實這只是一個極端的例子,當你初次接觸TDD時,寫這樣的代碼沒什么不好,以后當你熟練時就會發現這樣寫沒必要了,要記住,謙虛是通往TDD的必經之路!從經典開發方法轉向TDD就像從面向過程轉向面向對象一樣困難,你可能什么都懂,但你寫出來的類沒有一個純OO的!我的同事還告訴我真正的太極拳,其速度是很快的,不比任何一個快拳要慢,但是初學者(通常是指學習太極拳的前10年)太不容易把每個姿勢都做對,所以只能慢慢來。

            [什么場合不適用TDD?]
            問的好,確實有很多場合不適合使用TDD。比如對軟件質量要求極高的軍事或科研產品——神州六號,人命關天的軟件——醫療設備,等等,再比如設計很重要必須提前做好的軟件,這些都不適合TDD,但是不適合TDD不代表不能寫TestCase,只是作用不同,地位不同罷了。

            {Best Practise}

            [微笑面對編譯錯誤]
            學生時代最害怕的就是編譯錯誤,編譯錯誤可能會被老師視為上課不認真聽課的證據,或者同學間相互嘲笑的砝碼。甚至離開學校很多年的老程序員依然害怕它就像害怕遲到一樣,潛意識里似乎編譯錯誤極有可能和工資掛鉤(或者和智商掛鉤,反正都不是什么好事)。其實,只要提交到版本管理的代碼沒有編譯錯誤就可以了,不要擔心自己手上的代碼的編譯錯誤,通常,編譯錯誤都集中在下面三個方面:
            (1)你的代碼存在低級錯誤
            (2)由于某些Interface的實現尚不存在,所以被測試代碼無法編譯
            (3)由于某些代碼尚不存在,所以測試代碼無法編譯
            請注意第二點與第三點完全不同,前者表明設計已存在,而實現不存在導致的編譯錯誤;后者則指僅有TestCase而其它什么都沒有的情況,設計和實現都不存在,沒有Interface也沒有Implementation。

            另外,編譯器還有一個優點,那就是以最敏捷的身手告訴你,你的代碼中有那些錯誤。當然如果你擁有Eclipse這樣可以及時提示編譯錯誤的IDE,就不需要這樣的功能了。

            [重視你的計劃清單]
            在非TDD的情況下,尤其是傳統的瀑布模型的情況下,程序員不會不知道該做什么,事實上,總是有設計或者別的什么制品在引導程序員開發。但是在TDD的情況下,這種優勢沒有了,所以一個計劃清單對你來說十分重要,因為你必須自己發現該做什么。不同性格的人對于這一點會有不同的反應,我相信平時做事沒什么計劃要依靠別人安排的人(所謂將才)可能略有不適應,不過不要緊,Tasks和Calendar(又稱效率手冊)早已成為現代上班族的必備工具了;而平時工作生活就很有計劃性的人,比如我:),就會更喜歡這種自己可以掌控Plan的方式了。

            [廢黜每日代碼質量檢查]
            如果我沒有記錯的話,PSP對于個人代碼檢查的要求是蠻嚴格的,而同樣是在針對個人的問題上, TDD卻建議你廢黜每日代碼質量檢查,別起疑心,因為你總是在做TestCase要求你做的事情,并且總是有辦法(自動的)檢查代碼有沒有做到這些事情 ——紅燈停綠燈行,所以每日代碼檢查的時間可能被節省,對于一個嚴格的PSP實踐者來說,這個成本還是很可觀的!

            此外,對于每日代碼質量檢查的另一個好處,就是幫助你認識自己的代碼,全面的從宏觀、微觀、各個角度審視自己的成果,現在,當你依照TDD做事時,這個優點也不需要了,還記得前面說的TDD的第二個優點嗎,因為你已經全面的使用了一遍你的代碼,這完全可以達到目的。

            但是,問題往往也并不那么簡單,現在有沒有人能告訴我,我如何全面審視我所寫的測試用例呢?別忘了,它們也是以代碼的形式存在的哦。呵呵,但愿這個問題沒有把你嚇到,因為我相信到目前為止,它還不是瓶頸問題,況且在編寫產品代碼的時候你還是會自主的發現很多測試代碼上的沒考慮到的地方,可以就此修改一下。道理就是如此,世界上沒有任何方法能代替你思考的過程,所以也沒有任何方法能阻止你犯錯誤,TDD僅能讓你更容易發現這些錯誤而已。

            [如果無法完成一個大的測試,就從最小的開始]
            如果我無法開始怎么辦,教科書上有個很好的例子:我要寫一個電影列表的類,我不知道如何下手,如何寫測試用例,不要緊,首先想象靜態的結果,如果我的電影列表剛剛建立呢,那么它應該是空的,OK,就寫這個斷言吧,斷言一個剛剛初始化的電影列表是空的。這不是愚蠢,這是細節,奧運會五項全能的金牌得主瑪麗蓮·金是這樣說的:“成功人士的共同點在于……如果目標不夠清晰,他們會首先做通往成功道路上的每一個細小步驟……”。

            [嘗試編寫自己的xUnit]
            Kent Beck建議大家每當接觸一個新的語言或開發平臺的時候,就自己寫這個語言或平臺的xUnit,其實幾乎所有常用的語言和平臺都已經有了自己的 xUnit,而且都是大同小異,但是為什么大師給出了這樣的建議呢。其實Kent Beck的意思是說通過這樣的方式你可以很快的了解這個語言或平臺的特性,而且xUnit確實很簡單,只要知道原理很快就能寫出來。這對于那些喜歡自己寫底層代碼的人,或者喜歡控制力的人而言是個好消息。

            [善于使用Ctrl-C/Ctrl-V來編寫TestCase]
            不必擔心TestCase會有代碼冗余的問題,讓它冗余好了。

            [永遠都是功能First,改進可以稍后進行]
            上面這個標題還可以改成另外一句話:避免過渡設計!

            [淘汰陳舊的用例]
            舍不得孩子套不著狼。不要可惜陳舊的用例,因為它們可能從概念上已經是錯誤的了,或僅僅會得出錯誤的結果,或者在某次重構之后失去了意義。當然也不一定非要刪除它們,從TestSuite中除去(JUnit)或加上Ignored(NUnit)標簽也是一個好辦法。

            [用TestCase做試驗]
            如果你在開始某個特性或產品的開發之前對某個領域不太熟悉或一無所知,或者對自己在該領域里的能力一無所知,那么你一定會選擇做試驗,在有單元測試作工具的情況下,建議你用TestCase做試驗,這看起來就像你在寫一個驗證功能是否實現的 TestCase一樣,而事實上也一樣,只不過你所驗證的不是代碼本身,而是這些代碼所依賴的環境。

            [TestCase之間應該盡量獨立]
            保證單獨運行一個TestCase是有意義的。

            [不僅測試必須要通過的代碼,還要測試必須不能通過的代碼]
            這是一個小技巧,也是不同于設計思路的東西。像越界的值或者亂碼,或者類型不符的變量,這些輸入都可能會導致某個異常的拋出,或者導致一個標示“illegal parameters”的返回值,這兩種情況你都應該測試。當然我們無法枚舉所有錯誤的輸入或外部環境,這就像我們無法枚舉所有正確的輸入和外部環境一樣,只要TestCase能說明問題就可以了。

            [編寫代碼的第一步,是在TestCase中用Ctrl-C]
            這是一個高級技巧,呃,是的,我是這個意思,我不是說這個技巧難以掌握,而是說這個技巧當且僅當你已經是一個TDD高手時,你才能體會到它的魅力。多次使用TDD的人都有這樣的體會,既然我的TestCase已經寫的很好了,很能說明問題,為什么我的代碼不能從TestCase拷貝一些東西來呢。當然,這要求你的TestCase已經具有很好的表達能力,比如斷言f (5)=125的方式顯然沒有斷言f(5)=5^(5-2)表達更多的內容。

            [測試用例包應該盡量設計成可以自動運行的]
            如果產品是需要交付源代碼的,那我們應該允許用戶對代碼進行修改或擴充后在自己的環境下run整個測試用例包。既然通常情況下的產品是可以自動運行的,那為什么同樣作為交付用戶的制品,測試用例包就不是自動運行的呢?即使產品不需要交付源代碼,測試用例包也應該設計成可以自動運行的,這為測試部門或下一版本的開發人員提供了極大的便利。

            [只亮一盞紅燈]
            大師的建議,前面已經提到了,僅僅是建議。

            [用TestCase描述你發現的bug]
            如果你在另一個部門的同事使用了你的代碼,并且,他發現了一個bug,你猜他會怎么做?他會立即走到你的工位邊上,大聲斥責說:“你有bug!”嗎?如果他膽敢這樣對你,對不起,你一定要冷靜下來,不要當面回罵他,相反你可以微微一笑,然后心平氣和的對他說:“哦,是嗎?那么好吧,給我一個TestCase證明一下。”現在局勢已經倒向你這一邊了,如果他還沒有準備好回答你這致命的一擊,我猜他會感到非常羞愧,并在內心責怪自己太莽撞。事實上,如果他的TestCase沒有過多的要求你的代碼(而是按你們事前的契約),并且亮了紅燈,那么就可以確定是你的bug,反之,對方則無理了。用TestCase描述bug的另一個好處是,不會因為以后的修改而再次暴露這個bug,它已經成為你發布每一個版本之前所必須檢查的內容了。

            {關于單元測試}

            單元測試的目標是

            Keep the bar green to keep the code clean

            這句話的含義是,事實上我們只做兩件事情:讓代碼奏效(Keep the bar green)和讓代碼潔凈(Keep the code clean),前者是把事情做對,后者是把事情做好,兩者既是TDD中的兩頂帽子,又是xUnit架構中的因果關系。

            單元測試作為軟件測試的一個類別,并非是xUnit架構創造的,而是很早就有了。但是xUnit架構使得單元測試變得直接、簡單、高效和規范,這也是單元測試最近幾年飛速發展成為衡量一個開發工具和環境的主要指標之一的原因。正如Martin Fowler所說:“軟件工程有史以來從沒有如此眾多的人大大收益于如此簡單的代碼!”而且多數語言和平臺的xUnit架構都是大同小異,有的僅是語言不同,其中最有代表性的是JUnit和NUnit,后者是前者的創新和擴展。一個單元測試框架xUnit應該:1)使每個TestCase獨立運行;2)使每個TestCase可以獨立檢測和報告錯誤;3)易于在每次運行之前選擇TestCase。下面是我枚舉出的xUnit框架的概念,這些概念構成了當前業界單元測試理論和工具的核心:

            [測試方法/TestMethod]
            測試的最小單位,直接表示為代碼。

            [測試用例/TestCase]
            由多個測試方法組成,是一個完整的對象,是很多TestRunner執行的最小單位。

            [測試容器/TestSuite]
            由多個測試用例構成,意在把相同含義的測試用例手動安排在一起,TestSuite可以呈樹狀結構因而便于管理。在實現時,TestSuite形式上往往也是一個TestCase或TestFixture。

            [斷言/Assertion]
            斷言一般有三類,分別是比較斷言(如assertEquals),條件斷言(如isTrue),和斷言工具(如fail)。

            [測試設備/TestFixture]
            為每個測試用例安排一個SetUp方法和一個TearDown方法,前者用于在執行該測試用例或該用例中的每個測試方法前調用以初始化某些內容,后者在執行該測試用例或該用例中的每個方法之后調用,通常用來消除測試對系統所做的修改。

            [期望異常/Expected Exception]
            期望該測試方法拋出某種指定的異常,作為一個“斷言”內容,同時也防止因為合情合理的異常而意外的終止了測試過程。

            [種類/Category]
            為測試用例分類,實際使用時一般有TestSuite就不再使用Category,有Category就不再使用TestSuite。

            [忽略/Ignored]
            設定該測試用例或測試方法被忽略,也就是不執行的意思。有些被拋棄的TestCase不愿刪除,可以定為Ignored。

            [測試執行器/TestRunner]
            執行測試的工具,表示以何種方式執行測試,別誤會,這可不是在代碼中規定的,完全是與測試內容無關的行為。比如文本方式,AWT方式,swing方式,或者Eclipse的一個視圖等等。

            {實例:Fibonacci數列}

            下面的Sample展示TDDer是如何編寫一個旨在產生Fibonacci數列的方法。
            (1)首先寫一個TC,斷言fib(1) = 1;fib(2) = 1;這表示該數列的第一個元素和第二個元素都是1。

            public   void  testFab() {
                    assertEquals(
            1 , fib( 1 ));
                    assertEquals(
            1 , fib( 2 ));
            }

            (2)上面這段代碼不能編譯通過,Great!——是的,我是說Great!當然,如果你正在用的是Eclipse那你不需要編譯,Eclipse 會告訴你不存在fib方法,單擊mark會問你要不要新建一個fib方法,Oh,當然!為了讓上面那個TC能通過,我們這樣寫:

            public   int  fib(  int  n ) {
                    
            return   1 ;
            }

            (3)現在那個TC亮了綠燈,wow!應該慶祝一下了。接下來要增加TC的難度了,測第三個元素。

            public   void  testFab() {
                    assertEquals(
            1 , fib( 1 ));
                    assertEquals(
            1 , fib( 2 ));
                    assertEquals(
            2 , fib( 3 ));
            }

            不過這樣寫還不太好看,不如這樣寫:

            public   void  testFab() {
                    assertEquals(
            1 , fib( 1 ));
                    assertEquals(
            1 , fib( 2 ));
                    assertEquals(fib(
            1 ) + fib( 2 ), fib( 3 ));
            }

            (4)新增加的斷言導致了紅燈,為了扭轉這一局勢我們這樣修改fib方法,其中部分代碼是從上面的代碼中Ctrl-C/Ctrl-V來的:

            public   int  fib(  int  n ) {
                    
            if  ( n  ==   3  )  return  fib( 1 ) + fib( 2 );
                    
            return   1 ;
            }

            (5)天哪,這真是個賤人寫的代碼!是啊,不是嗎?因為TC就是產品的藍本,產品只要恰好滿足TC就ok。所以事情發展到這個地步不是fib方法的錯,而是TC的錯,于是TC還要進一步要求:

            public   void  testFab() {
                    assertEquals(
            1 , fib( 1 ));
                    assertEquals(
            1 , fib( 2 ));
                    assertEquals(fib(
            1 ) + fib( 2 ), fib( 3 ));
                    assertEquals(fib(
            2 ) + fib( 3 ), fib( 4 ));
            }

            (6)上有政策下有對策。

            public   int  fib(  int  n ) {
                    
            if  ( n  ==   3  )  return  fib( 1 ) + fib( 2 );
                    
            if  ( n  ==   4  )  return  fib( 2 ) + fib( 3 );
                    
            return   1 ;
            }

            (7)好了,不玩了。現在已經不是賤不賤的問題了,現在的問題是代碼出現了冗余,所以我們要做的是——重構:

            public   int  fib(  int  n ) {
                    
            if  ( n  ==   1   ||  n  ==   2  )  return   1 ;
                    
            else   return  fib( n  -   1  )  +  fib( n  -   2  );
            }

            (8)好,現在你已經fib方法已經寫完了嗎?錯了,一個危險的錯誤,你忘了錯誤的輸入了。我們令0表示Fibonacci中沒有這一項。

            public   void  testFab() {
                    assertEquals(
            1 , fib( 1 ));
                    assertEquals(
            1 , fib( 2 ));
                    assertEquals(fib(
            1 ) + fib( 2 ), fib( 3 ));
                    assertEquals(fib(
            2 ) + fib( 3 ), fib( 4 ));
                    assertEquals(
            0 , fib( 0 ));
                    assertEquals(
            0 , fib( - 1 ));
            }

            then change the method fib to make the bar grean:

            public   int  fib(  int  n ) {
                    
            if  ( n  <=   0  )  return   0 ;
                    
            if  ( n  ==   1   ||  n  ==   2  )  return   1 ;
                    
            else   return  fib( n  -   1  )  +  fib( n  -   2  );
            }

            (9)下班前最后一件事情,把TC也重構一下:

            public   void  testFab() {
                    
            int  cases[][]  =  {
                            {
            0 0 }, { - 1 0 },   // the wrong parameters
                            { 1 1 }, { 2 1 }};   // the first 2 elements

                    
            for  ( int  i  =   0 ; i  <  cases.length; i ++ )
                            assertEquals( cases[i][
            1 ], fib(cases[i][ 0 ]) );

                    
            // the rest elements
                     for  ( int  i  =   3 ; i  <   20 ; i ++ )
                            assertEquals(fib(i
            - 1 ) + fib(i - 2 ), fib(i));
            }

            (10)打完收工。

            {關于本文的寫作}

            在本文的寫作過程中,作者也用到了TDD的思維,事實上作者先構思要寫一篇什么樣的文章,然后寫出這篇文章應該滿足的幾個要求,包括功能的要求(要寫些什么)和性能的要求(可讀性如何)和質量的要求(文字的要求),這些要求起初是一個也達不到的(因為正文還一個字沒有),在這種情況下作者的文章無法編譯通過,為了達到這些要求,作者不停的寫啊寫啊,終于在花盡了兩個月的心血之后完成了當初既定的所有要求(make the bar green),隨后作者整理了一下文章的結構(重構),在滿意的提交給了Blog系統之后,作者穿上了一件綠色的汗衫,趴在地上,學了兩聲青蛙叫。。。。。。。^_^

            {后記:Martin Fowler在中國}

            從本文正式完成到發表的幾個小時里,我偶然讀到了Martin Fowler先生北京訪談錄,其間提到了很多對測試驅動開發的看法,摘抄在此:

            Martin Fowler:當然(值得花一半的時間來寫單元測試)!因為單元測試能夠使你更快的完成工作。無數次的實踐已經證明這一點。你的時間越是緊張,就越要寫單元測試,它看上去慢,但實際上能夠幫助你更快、更舒服地達到目的。
            Martin Fowler:什么叫重要?什么叫不重要?這是需要逐漸認識的,不是想當然的。我為絕大多數的模塊寫單元測試,是有點煩人,但是當你意識到這工作的價值時,你會欣然的。
            Martin Fowler:對全世界的程序員我都是那么幾條建議:……第二,學習測試驅動開發,這種新的方法會改變你對于軟件開發的看法。……

            ——《程序員》,2005年7月刊

            {鳴謝}

            fhawk
            Dennis Chen
            般若菩提
            Kent Beck
            Martin Fowler
            c2.com

            posted on 2008-03-30 16:42 不倦 閱讀(389) 評論(0)  編輯 收藏 引用

            久久涩综合| 久久久精品久久久久特色影视| 国产69精品久久久久观看软件| 精品一二三区久久aaa片| 色偷偷偷久久伊人大杳蕉| 嫩草影院久久99| 久久久久亚洲AV片无码下载蜜桃 | 精品国产乱码久久久久久1区2区| 久久精品国产网红主播| 精品国产一区二区三区久久蜜臀| 亚洲性久久久影院| 天天久久狠狠色综合| 狠狠色综合网站久久久久久久高清| 久久美女人爽女人爽| 香蕉久久夜色精品升级完成| 草草久久久无码国产专区| 亚洲欧美成人综合久久久| 很黄很污的网站久久mimi色| 久久青青草原亚洲av无码app| 亚洲国产精品狼友中文久久久| 国内精品久久久久久99| 久久人妻AV中文字幕| 无码任你躁久久久久久老妇| 国产AⅤ精品一区二区三区久久| 午夜天堂精品久久久久| 久久91精品国产91| 亚洲国产成人久久笫一页| 久久成人永久免费播放| 精品国产青草久久久久福利| 久久噜噜电影你懂的| 99久久精品影院老鸭窝| 日产精品99久久久久久| 亚洲AV无码久久| 久久久婷婷五月亚洲97号色| 性欧美大战久久久久久久久| 亚洲精品乱码久久久久久中文字幕| 久久国内免费视频| 久久亚洲春色中文字幕久久久| 无码伊人66久久大杳蕉网站谷歌 | 久久久久久久综合日本| 久久精品国产WWW456C0M|