版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的藝術(shù)注:因內(nèi)容過(guò)長(zhǎng)上傳受限制,本文檔只顯示部分內(nèi)容,完整版文檔請(qǐng)下載此文檔后留言謝謝。目錄\h第一部分TDD入門\h第1章綜述\h1.1挑戰(zhàn):用正確的方法解決正確的問(wèn)題\h1.2解決方案:測(cè)試驅(qū)動(dòng)\h1.3正確地做事:TDD\h1.4做正確的事:ATDD\h1.5TDD工具\(yùn)h1.6小結(jié)\h第2章TDD入門\h2.1從需求到測(cè)試\h2.2選擇第一個(gè)測(cè)試\h2.3廣度優(yōu)先,深度優(yōu)先\h2.4別忘了重構(gòu)\h2.5添加錯(cuò)誤處理\h2.6無(wú)窮盡的測(cè)試\h2.7小結(jié)\h第3章小步重構(gòu)\h3.1探尋解決方案\h3.2以受控的方式修改設(shè)計(jì)\h3.3進(jìn)一步延伸新設(shè)計(jì)\h3.4小結(jié)\h第4章TDD的概念與模式\h4.1如何編寫及通過(guò)測(cè)試\h4.2重要的測(cè)試概念\h4.3近處觀察測(cè)試替身\h4.4提高設(shè)計(jì)的可測(cè)試性的準(zhǔn)則\h4.5單元測(cè)試模式\h4.6在遺留代碼基礎(chǔ)上工作\h4.7小結(jié)\h第二部分針對(duì)特定技術(shù)應(yīng)用TDD\h第5章測(cè)試驅(qū)動(dòng)Web組件\h5.1在60秒內(nèi)介紹Web應(yīng)用中的MVC\h5.2控制器\h5.3用測(cè)試先行的方法構(gòu)建視圖\h5.4在基于控件的Web框架基礎(chǔ)上TDD\h5.5小結(jié)\h第6章測(cè)試驅(qū)動(dòng)數(shù)據(jù)訪問(wèn)\h6.1探索問(wèn)題領(lǐng)域\h6.2用單元測(cè)試驅(qū)動(dòng)數(shù)據(jù)訪問(wèn)\h6.3編碼前寫集成測(cè)試\h6.4集成測(cè)試實(shí)戰(zhàn)\h6.5為集成測(cè)試填充數(shù)據(jù)\h6.6使用單元測(cè)試還是集成測(cè)試\h6.7文件系統(tǒng)訪問(wèn)\h6.8小結(jié)\h第7章測(cè)試驅(qū)動(dòng)不可預(yù)測(cè)功能\h7.1測(cè)試驅(qū)動(dòng)時(shí)間相關(guān)功能\h7.2測(cè)試驅(qū)動(dòng)多線程代碼\h7.3標(biāo)準(zhǔn)同步對(duì)象\h7.4小結(jié)\h第8章測(cè)試驅(qū)動(dòng)Swing代碼\h8.1SwingUI中該測(cè)試什么\h8.2可測(cè)試UI代碼的模式\h8.3測(cè)試視圖控件的工具\(yùn)h8.4測(cè)試驅(qū)動(dòng)視圖組件\h8.5小結(jié)\h第三部分基于ATDD構(gòu)建產(chǎn)品\h第9章解析驗(yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā)\h9.1用戶故事介紹\h9.2驗(yàn)收測(cè)試\h9.3理解過(guò)程\h9.4作為團(tuán)隊(duì)活動(dòng)的ATDD\h9.5ATDD的好處\h9.6我們究竟要測(cè)試什么\h9.7工具概覽\h9.8小結(jié)\h第10章用Fit創(chuàng)建驗(yàn)收測(cè)試\h10.1Fit是什么\h10.2三個(gè)內(nèi)建夾具\(yùn)h10.3FitLibrary對(duì)內(nèi)建夾具的擴(kuò)展\h10.4執(zhí)行Fit測(cè)試\h10.5小結(jié)\h第11章執(zhí)行驗(yàn)收測(cè)試的策略\h11.1驗(yàn)收測(cè)試該檢測(cè)什么\h11.2實(shí)現(xiàn)方式\h11.3技術(shù)相關(guān)考慮\h11.4常見(jiàn)問(wèn)題的處理技巧\h11.5小結(jié)\h第12章TDD應(yīng)用\h12.1成功采用TDD的必要條件\h12.2讓其他人參與進(jìn)來(lái)\h12.3如何應(yīng)對(duì)阻力\h12.4如何推進(jìn)變革\h12.5小結(jié)\h附錄AJUnit4簡(jiǎn)明教程\h附錄BJUnit3.8簡(jiǎn)明教程\h附錄CEasyMock簡(jiǎn)明教程\h附錄D通過(guò)Ant運(yùn)行測(cè)試
第一部分TDD入門這一部分是TDD(Test-DrivenDevelopment)入門,引領(lǐng)讀者體驗(yàn)測(cè)試驅(qū)動(dòng)的藝術(shù)。第1章來(lái)認(rèn)識(shí)TDD和它的延伸概念——驗(yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(AcceptanceTDD,ATDD),我們從最基本的知識(shí)入手,對(duì)這兩種技術(shù)先有一個(gè)大概的了解。第2章進(jìn)入實(shí)際動(dòng)手環(huán)節(jié),將通過(guò)修改和運(yùn)行實(shí)際代碼深入體驗(yàn)測(cè)試先行的好處。第3章將更進(jìn)一步,向讀者展示大規(guī)模重構(gòu)的例子,借以看看設(shè)計(jì)將會(huì)有多么顯著的變化。幾年來(lái),通過(guò)向一批批的程序員講授TDD,我體會(huì)到實(shí)踐是最好的老師。在學(xué)習(xí)了第2章和第3章并親手實(shí)現(xiàn)功能完善的模板引擎之后,就可以接觸一些重量級(jí)的技術(shù)了。第4章介紹了運(yùn)用TDD思想的一些技巧和方法,包括如何選擇下一個(gè)測(cè)試并使其通過(guò)。此外,必要時(shí)還將討論相關(guān)的設(shè)計(jì)原則和測(cè)試工具。本部分內(nèi)容第1章綜述第2章TDD入門第3章小步重構(gòu)第4章TDD的概念與模式
第1章綜述我能忍受暴力,但施暴的理由卻讓我無(wú)法容忍。——奧斯卡·王爾德測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(Test-DrivenDevelopment,TDD1),用一句話講,就是“寫代碼只為修復(fù)失敗了的測(cè)試”。我們先寫一個(gè)測(cè)試,然后再寫代碼讓測(cè)試通過(guò)。當(dāng)我們?cè)诋?dāng)前結(jié)構(gòu)中找出最佳設(shè)計(jì)時(shí),由于有足夠的測(cè)試做保障,我們可以放心地改動(dòng)現(xiàn)有設(shè)計(jì)而不必?fù)?dān)心破壞已完成的功能。使用這種開(kāi)發(fā)方法,我們可以讓設(shè)計(jì)更加優(yōu)良,能編寫出可測(cè)試的代碼,同時(shí)還能避免在不切實(shí)際的假設(shè)基礎(chǔ)上過(guò)度地設(shè)計(jì)系統(tǒng)。要得到這些好處,只需不斷添加可執(zhí)行測(cè)試,一步步地驅(qū)動(dòng)設(shè)計(jì),從而最終實(shí)現(xiàn)整個(gè)系統(tǒng)。1縮寫TDD有時(shí)也代表測(cè)試驅(qū)動(dòng)設(shè)計(jì)(Test-DrivenDesign)。有人把TDD稱為用測(cè)試先行編程(Test-FirstProgramming),只不過(guò)叫法有區(qū)別而已。這本書就是講如何實(shí)行這每個(gè)小步驟的。在后面的章節(jié)中,我們將會(huì)學(xué)到TDD的本質(zhì)以及玄妙之處,學(xué)到如何利用TDD開(kāi)發(fā)普通Java應(yīng)用程序及企業(yè)級(jí)Java應(yīng)用程序,以及如何用ATDD(AcceptanceTDD,驗(yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā),測(cè)試驅(qū)動(dòng)開(kāi)發(fā)核心理念的一個(gè)擴(kuò)展)來(lái)驅(qū)動(dòng)整個(gè)開(kāi)發(fā)過(guò)程。在用測(cè)試驅(qū)動(dòng)開(kāi)發(fā)實(shí)現(xiàn)某個(gè)具體功能之前,我們將會(huì)首先編寫功能測(cè)試或驗(yàn)收測(cè)試,從系統(tǒng)功能角度驅(qū)動(dòng)開(kāi)發(fā)過(guò)程。TDD其實(shí)并不是一個(gè)新概念。很久以前,不少開(kāi)發(fā)人員就認(rèn)為編寫測(cè)試不僅僅是為了驗(yàn)證系統(tǒng)正確性了。至今你還可以從他們那里聽(tīng)到這個(gè)故事:從那時(shí)候起,我們寫代碼之前都會(huì)先寫測(cè)試……。而現(xiàn)在,這種開(kāi)發(fā)方法有了自己的名字——TDD。本書的大部分內(nèi)容都是關(guān)于“如何測(cè)試驅(qū)動(dòng)”以及“測(cè)試驅(qū)動(dòng)什么”的。這些知識(shí)可以被用到各種日常軟件開(kāi)發(fā)任務(wù)中。不過(guò),相對(duì)于主流開(kāi)發(fā)方法,TDD仍然很新。就像今天的日常用品曾經(jīng)都是十足的奢侈品一樣,新的開(kāi)發(fā)或設(shè)計(jì)方法,通常都是只有資深的開(kāi)發(fā)人員才能擁有的高級(jí)貨,而在很多年以后,等這些先驅(qū)證明了新方法確實(shí)有效,這種方法才會(huì)被廣泛接受,成為開(kāi)發(fā)必備技能。我相信TDD會(huì)逐漸地變成主流開(kāi)發(fā)方法,而且我認(rèn)為,TDD已經(jīng)開(kāi)始變?yōu)橹髁鏖_(kāi)發(fā)方法了。希望本書能夠推動(dòng)這個(gè)過(guò)程。在本書的開(kāi)始部分,我們會(huì)先討論現(xiàn)有的開(kāi)發(fā)方法在開(kāi)發(fā)軟件過(guò)程中遇到的挑戰(zhàn)。一旦對(duì)現(xiàn)存問(wèn)題有了基本共識(shí),我們將著手討論如何用TDD或ATDD解決這些問(wèn)題,同時(shí)也會(huì)學(xué)著使用一些支持此開(kāi)發(fā)方法的工具。1.1挑戰(zhàn):用正確的方法解決正確的問(wèn)題開(kāi)發(fā)軟件的目的是為協(xié)助組織的經(jīng)營(yíng)運(yùn)作。作為一個(gè)專業(yè)的軟件開(kāi)發(fā)人員,我們的主要任務(wù)是向客戶交付一個(gè)能夠真正幫助他們提高工作效率并減少運(yùn)作成本的系統(tǒng)?;仡櫸叶嗄甑膶I(yè)軟件開(kāi)發(fā)經(jīng)驗(yàn),參閱幾十年來(lái)軟件開(kāi)發(fā)的文獻(xiàn)記載,再看看整個(gè)世界范圍內(nèi)的技術(shù)人員對(duì)于開(kāi)發(fā)方法的爭(zhēng)論,我們不難看出很多開(kāi)發(fā)組織本應(yīng)向他們的客戶交付更好的軟件。換言之,我們現(xiàn)在交付的軟件可不怎么好用。就算這些軟件能夠正常的工作,它們也沒(méi)有真正解決客戶的問(wèn)題。從本質(zhì)上講,我們寫出的代碼并沒(méi)有滿足客戶的真正需求。接下來(lái),我們來(lái)看看為什么“糟糕的代碼質(zhì)量”和“不能滿足客戶日益變化的需求”會(huì)妨礙我們給客戶交付足夠好用的系統(tǒng)。1.1.1糟糕的代碼質(zhì)量雖然軟件開(kāi)發(fā)行業(yè)已經(jīng)發(fā)展了很多年,但是開(kāi)發(fā)出軟件的質(zhì)量依舊存在問(wèn)題。近些年市場(chǎng)越來(lái)越注重軟件的及時(shí)上市,對(duì)軟件產(chǎn)品的需求量越來(lái)越大,同時(shí)很多的新技術(shù)也不斷涌現(xiàn)。在這種情形下,軟件行業(yè)將不可避免地繼續(xù)面臨質(zhì)量問(wèn)題。這些質(zhì)量問(wèn)題主要表現(xiàn)在兩個(gè)方面:高缺陷率和低可維護(hù)性。在缺陷的泥潭中掙扎缺陷會(huì)帶來(lái)很多額外的開(kāi)銷,因?yàn)樗鼤?huì)導(dǎo)致軟件不穩(wěn)定,行為不可預(yù)測(cè),或者完全不能使用。缺陷減少了軟件本身的價(jià)值,有時(shí)甚至使軟件造成的破壞遠(yuǎn)大于創(chuàng)造出的價(jià)值。測(cè)試可以解決這些問(wèn)題——我們仔細(xì)地檢查軟件是否像期望的那樣工作,同時(shí)也試著通過(guò)某種方式檢測(cè)其是否穩(wěn)定。測(cè)試在軟件開(kāi)發(fā)中的重要性是毋庸置疑的,但是傳統(tǒng)的測(cè)試方法只會(huì)在整個(gè)軟件開(kāi)發(fā)完畢并且代碼“凍結(jié)”后才進(jìn)行,而且會(huì)耗費(fèi)很長(zhǎng)時(shí)間。這種測(cè)試方法有很大的改進(jìn)空間。例如,在測(cè)試階段修復(fù)一個(gè)缺陷的成本,通常是在編碼階段就修復(fù)這個(gè)缺陷成本的兩倍或者更多。有缺陷的軟件是不能交付的。我們?cè)趯ふ也⑿迯?fù)缺陷上用的時(shí)間越多,開(kāi)銷越大,我們開(kāi)發(fā)軟件的能力也越低。軟件缺陷通常是由低質(zhì)量的代碼引起的,要維護(hù)這些代碼簡(jiǎn)直就是噩夢(mèng),而想對(duì)其進(jìn)行進(jìn)一步開(kāi)發(fā)也會(huì)舉步維艱,代價(jià)高昂。維護(hù)困難,開(kāi)發(fā)緩慢好的代碼有很多優(yōu)點(diǎn):整體設(shè)計(jì)優(yōu)秀,清楚地劃分出了各部分之間的功能和責(zé)任,并且沒(méi)有重復(fù)。而糟糕的代碼可不是這樣,成天和這種代碼打交道簡(jiǎn)直就是噩夢(mèng)。這些代碼很難讀懂,修改也很困難,因此工作效率會(huì)很低。改這里,還會(huì)莫名其妙地破壞掉那里的功能。重復(fù)的代碼使修復(fù)缺陷也變得不容易,改完一處,還要找出其他所有重復(fù)的代碼,挨個(gè)修正同樣的缺陷才算完。糟糕的代碼帶來(lái)的問(wèn)題還遠(yuǎn)不止這些。“我不想碰它,那任務(wù)永遠(yuǎn)不可能完成,如果做了,天知道會(huì)破壞掉其他什么功能?!边@是軟件業(yè)需要解決的一個(gè)很現(xiàn)實(shí)的問(wèn)題。想修改或者添加新功能時(shí),應(yīng)該是基于現(xiàn)有系統(tǒng)繼續(xù)開(kāi)發(fā),而不是重寫,這就是可維護(hù)性的重要之處。有良好的可維護(hù)性,我們才能及時(shí)應(yīng)對(duì)迅速變化的業(yè)務(wù)需求。如果代碼的維護(hù)性差,那么我們行動(dòng)和響應(yīng)的速度會(huì)變慢,按時(shí)交付的壓力會(huì)變大,這會(huì)促使開(kāi)發(fā)人員寫出質(zhì)量更差的代碼。如果想持續(xù)地交付軟件,我們必須打破這種惡性循環(huán)。這些問(wèn)題已經(jīng)夠糟糕了,但是還有其他問(wèn)題:我們交付的軟件通常都沒(méi)有完全滿足客戶的需求。1.1.2不能滿足客戶需求沒(méi)人愿意花錢當(dāng)冤大頭,但軟件行業(yè)的客戶就經(jīng)常被逼著這么做。在軟件開(kāi)發(fā)前期,客戶和開(kāi)發(fā)人員互相交換規(guī)格文檔,然后開(kāi)發(fā)工作就此開(kāi)始。十二個(gè)月以后客戶拿到系統(tǒng),才發(fā)現(xiàn)這并不是自己當(dāng)初想要的。更不用說(shuō)在當(dāng)前激烈的商戰(zhàn)下,客戶的業(yè)務(wù)需求和十二個(gè)月以前常常大相徑庭。由于交付的系統(tǒng)經(jīng)常不能真正滿足客戶需求,軟件開(kāi)發(fā)行業(yè)一直在嘗試用新的方法開(kāi)發(fā)軟件。我們?cè)囍诟鞣N規(guī)格文檔上多花些功夫,但效果卻適得其反。我們?cè)囍娱L(zhǎng)交付系統(tǒng)的時(shí)間,卻導(dǎo)致更多的人參與進(jìn)開(kāi)發(fā)過(guò)程。生產(chǎn)軟件是為了支持人工作,但更多的人卻為了生產(chǎn)軟件而工作。另外,在開(kāi)發(fā)初期確定過(guò)多的細(xì)節(jié)會(huì)導(dǎo)致文檔不可靠。由于細(xì)節(jié)上的假設(shè)環(huán)環(huán)相扣,規(guī)格文件中的錯(cuò)誤能輕易拖垮整個(gè)項(xiàng)目。軟件行業(yè)的業(yè)績(jī)不佳,但也不必因此沮喪,因?yàn)檫@些問(wèn)題有辦法解決。敏捷軟件開(kāi)發(fā)1——包括ExtremeProgramming(極限編程)和Scrum等方法——就是解決這些問(wèn)題的良藥。本書余下的內(nèi)容會(huì)詳細(xì)介紹敏捷開(kāi)發(fā)方法的核心實(shí)踐——測(cè)試驅(qū)動(dòng)。1CraigLarman所著的Agile&IterativeDevelopment:AManager'sGuide(Addison-Wesley,2003)一書很好地介紹了敏捷開(kāi)發(fā)方法。1.2解決方案:測(cè)試驅(qū)動(dòng)我們遇到的問(wèn)題可以分為兩個(gè)方面:代碼質(zhì)量不高和不能滿足客戶需求。解決方法也存在兩個(gè)方面:學(xué)會(huì)正確地構(gòu)建系統(tǒng),以及學(xué)會(huì)構(gòu)建正確的系統(tǒng)。本書所介紹的測(cè)試驅(qū)動(dòng)方法能很相似地應(yīng)用在這兩方面,差別只在于如何利用測(cè)試來(lái)構(gòu)建出維護(hù)性高并能真正滿足客戶需求的軟件。在細(xì)節(jié)層面上,我們用TDD方法以測(cè)試驅(qū)動(dòng)的方式編寫代碼。在較高的,即軟件的特性和功能層面上,我們使用類似的ATDD方法以測(cè)試驅(qū)動(dòng)的方式構(gòu)建系統(tǒng)。圖1-1從提高內(nèi)部質(zhì)量和外部質(zhì)量角度上描述了這種方法組合。從圖1-1中可以看出,在這兩個(gè)不同的層面上結(jié)合使用測(cè)試驅(qū)動(dòng),能保證軟件的內(nèi)部質(zhì)量,同時(shí)能保證可見(jiàn)的外部質(zhì)量。在下面的小節(jié)里,我們將會(huì)討論TDD和ATDD是如何帶來(lái)這些改進(jìn)的。在深入技術(shù)細(xì)節(jié)之前,先看看測(cè)試驅(qū)動(dòng)如何幫我們應(yīng)付軟件開(kāi)發(fā)中的挑戰(zhàn)吧。圖1-1TDD用來(lái)提高軟件的內(nèi)部質(zhì)量,而驗(yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā)保證開(kāi)發(fā)出的軟件能滿足正確的功能需求1.2.1高質(zhì)量的TDDTDD鼓勵(lì)優(yōu)良設(shè)計(jì),這種紀(jì)律嚴(yán)明的方式還能幫我們避免在開(kāi)發(fā)中引入錯(cuò)誤。用TDD開(kāi)發(fā),我們會(huì)寫很多小的自動(dòng)化測(cè)試,這些測(cè)試最終會(huì)組成一個(gè)有效的預(yù)警系統(tǒng)以防止代碼蛻化。沒(méi)人能在現(xiàn)有代碼中憑空加入“質(zhì)量”,而提倡短開(kāi)發(fā)周期的TDD從項(xiàng)目開(kāi)始就能保證較高的代碼質(zhì)量,并一直延續(xù)下去。這種短開(kāi)發(fā)周期的開(kāi)發(fā)方式與舊方式有很大不同。我們習(xí)慣于先設(shè)計(jì),然后編碼實(shí)現(xiàn),最后做一些并不完備的測(cè)試。(我們都是優(yōu)秀的程序員,很少犯錯(cuò),所以稍加測(cè)試即可,不是嗎?)TDD完全顛倒了整個(gè)過(guò)程。我們會(huì)先寫測(cè)試描述出目標(biāo),然后寫代碼達(dá)到這個(gè)清晰的目標(biāo),最后再設(shè)計(jì)——在已有代碼的基礎(chǔ)上找出最簡(jiǎn)單的設(shè)計(jì)。開(kāi)發(fā)周期的最后一步叫作重構(gòu)。重構(gòu)是種嚴(yán)格的方法,用于改變代碼結(jié)構(gòu),消除重復(fù),并改良設(shè)計(jì)。我們能通過(guò)持續(xù)重構(gòu)逐漸地提升代碼質(zhì)量。如果你還不夠了解TDD的完整周期,不用擔(dān)心,我們將會(huì)在1.3節(jié)中詳細(xì)介紹。回顧一下TDD的特點(diǎn):這種開(kāi)發(fā)方法能幫我們寫出完全可測(cè)試的代碼,在每個(gè)階段演化出當(dāng)前最佳的設(shè)計(jì),還能幫我們避免陷入代碼越寫越糟的惡性循環(huán)。說(shuō)到質(zhì)量,下面我們來(lái)討論“質(zhì)量”這相對(duì)抽象概念的具體含義,以及它對(duì)我們而言意味著什么。不同形式的“質(zhì)量”正如業(yè)內(nèi)質(zhì)量保證部門所見(jiàn)證的,人們傾向于把“質(zhì)量”與軟件的缺陷數(shù)量聯(lián)系在一起?!百|(zhì)量”也可能表示軟件對(duì)客戶需求的滿足程度。還有人認(rèn)為“質(zhì)量”表示的不僅是可見(jiàn)的外部質(zhì)量,還應(yīng)包括內(nèi)部質(zhì)量(這又可以表現(xiàn)為外部質(zhì)量,例如開(kāi)發(fā)成本維護(hù)成本等)。TDD可以提高所有這些不同定義的“質(zhì)量”,因?yàn)樗举|(zhì)上有助于改良設(shè)計(jì)及提高代碼質(zhì)量。測(cè)試沒(méi)能覆蓋所有代碼分支是缺陷流入產(chǎn)品的首要原因。(或者是因?yàn)槲覀冇行卸?,沒(méi)有認(rèn)真地執(zhí)行所有測(cè)試——至少不太認(rèn)真,所以讓缺陷漏過(guò)去了。)使用TDD,我們能保證所有的代碼都是有用的1,而且都會(huì)被測(cè)試覆蓋。因?yàn)橛懈吒采w率的自動(dòng)化測(cè)試,TDD能保證每個(gè)測(cè)試覆蓋的功能都不會(huì)出現(xiàn)問(wèn)題。因此,如何提高質(zhì)量的問(wèn)題就轉(zhuǎn)化成了如何編寫高質(zhì)量測(cè)試的問(wèn)題,解決了這個(gè)問(wèn)題,質(zhì)量問(wèn)題也就迎刃而解了。1每行代碼都是為了通過(guò)測(cè)試而寫,所以不會(huì)存在無(wú)用的代碼?!g者注編寫高質(zhì)量的測(cè)試有一定的技巧,例如需要針對(duì)正常執(zhí)行路徑、邊界值以及可能的錯(cuò)誤操作分別做測(cè)試。在這方面,TDD可以促使我們從外在接口的角度考慮模塊和類的設(shè)計(jì)。由于不用先考慮實(shí)現(xiàn)細(xì)節(jié),我們可以站在更恰當(dāng)?shù)慕嵌仍O(shè)想這個(gè)類的行為以及其他開(kāi)發(fā)人員可能的使用方式,因而更容易發(fā)現(xiàn)代碼中存在問(wèn)題。由于TDD關(guān)注高質(zhì)量的代碼及優(yōu)良的設(shè)計(jì),以往用于調(diào)試的大量時(shí)間,現(xiàn)在都被用在實(shí)現(xiàn)用戶需要的功能及改良現(xiàn)有系統(tǒng)的設(shè)計(jì)上了。花更少的時(shí)間修復(fù)缺陷TDD能幫我們縮短修復(fù)缺陷所用的時(shí)間。在引入缺陷的兩個(gè)月后再去修復(fù)顯然比起立即修復(fù)代價(jià)要大得多,因此有必要盡力減少引入缺陷的數(shù)量,盡快發(fā)現(xiàn)軟件中已有的缺陷。若能保證寫代碼時(shí)測(cè)試先行地小步前進(jìn),那么調(diào)試器也基本沒(méi)用了,因?yàn)橹挥行绿砑拥膸仔写a才可能破壞測(cè)試,所以找出問(wèn)題會(huì)很容易,也不會(huì)像傳說(shuō)中的高手那樣長(zhǎng)時(shí)間地調(diào)試代碼了。我們可以更快地修復(fù)缺陷,相應(yīng)的開(kāi)發(fā)成本也會(huì)降低。每個(gè)遺漏缺陷都可能造成幾百甚至幾萬(wàn)元的損失2,可不是一筆小數(shù)目啊。不用連續(xù)幾個(gè)小時(shí)調(diào)試程序,我們也有更多的時(shí)間做其他更有意義的事情了。2\h/Papers/Costtofixdefect.html更快速地完成用戶所需要的功能有很多好處:我們有更多的時(shí)間用于清理代碼,學(xué)習(xí)最新的工具和開(kāi)發(fā)技術(shù),趕上其他優(yōu)秀的同事,等等。這些會(huì)幫助我們提高代碼質(zhì)量,變得更加自信,做事也更快。這樣一來(lái),我們TDD又會(huì)變得更有效。這種良性循環(huán)帶來(lái)的提升空間幾乎是無(wú)限的。我們一會(huì)兒將會(huì)討論TDD給我們開(kāi)發(fā)人員帶來(lái)的更多實(shí)惠,不過(guò)在這之前,我們先介紹ATDD吧。1.2.2用ATDD滿足客戶需求TDD能從技術(shù)角度幫我們提高代碼質(zhì)量,使代碼執(zhí)行結(jié)果正確,容易理解,也容易維護(hù)。不過(guò)用TDD寫出的代碼的驗(yàn)證邏輯針對(duì)的是獨(dú)立的代碼塊,而不是系統(tǒng)的具體功能。而且用測(cè)試先行的方法寫出的最棒的代碼也有可能做錯(cuò)事情,也許做出的功能不是客戶想要的。因此,使用ATDD很有必要。要給系統(tǒng)添加新的功能,傳統(tǒng)的做法是先寫出需求文檔,然后開(kāi)發(fā)人員按照文檔開(kāi)發(fā),完成后進(jìn)行內(nèi)部測(cè)試,最后交給客戶做驗(yàn)收測(cè)試。ATDD則有些不同:我們會(huì)先寫測(cè)試然后再實(shí)現(xiàn)功能,如圖1-2所示。換言之,我們把客戶需求轉(zhuǎn)化成一系列可執(zhí)行的測(cè)試,開(kāi)發(fā)工作會(huì)基于這些測(cè)試,而不是基于開(kāi)發(fā)人員各自對(duì)需求文檔的不同理解。ATDD拉近了最終用戶和開(kāi)發(fā)人員的距離,這是生產(chǎn)優(yōu)秀軟件的必要條件。在ATDD中,客戶與開(kāi)發(fā)團(tuán)隊(duì)會(huì)緊密地合作,定義出清晰的毫無(wú)歧義的測(cè)試,而不是花大量的時(shí)間寫含糊不清的需求文檔。這些驗(yàn)收測(cè)試能準(zhǔn)確地告訴我們客戶要求的功能是否已經(jīng)完成。由于用戶的需求全都轉(zhuǎn)換成了可執(zhí)行的具體的功能測(cè)試,因此可以確保開(kāi)發(fā)出的軟件切實(shí)地滿足了客戶的需求。圖1-2ATDD用自動(dòng)化的可執(zhí)行測(cè)試驅(qū)動(dòng)新功能的開(kāi)發(fā)這個(gè)過(guò)程和代碼層面上的TDD很相似。用ATDD開(kāi)發(fā)軟件,我們會(huì)更關(guān)注系統(tǒng)行為的測(cè)試,而不是對(duì)象行為的測(cè)試。因此我們也更需要使用客戶和開(kāi)發(fā)人員都理解的語(yǔ)言交流。TDD和ATDD需要配合使用。在系統(tǒng)層面上使用ATDD驅(qū)動(dòng)開(kāi)發(fā)過(guò)程,而在每個(gè)功能點(diǎn)的實(shí)現(xiàn)層面上則使用TDD。這兩種TDD方法之所以要結(jié)合使用,并不是因?yàn)榇嬖诰o耦合,而是因?yàn)檫@樣可以使其互相彌補(bǔ),互相支撐,而且能夠更好地發(fā)揮各自優(yōu)勢(shì)。我們現(xiàn)在應(yīng)該已經(jīng)了解了,TDD和ATDD如何能幫我們交付高質(zhì)量且滿足客戶需求的軟件。我們隨即將會(huì)深入了解TDD是什么,為什么能夠幫我們開(kāi)發(fā)出高質(zhì)量的軟件,以及如何“用正確的方法做事”。在1.4節(jié)中,我們將會(huì)討論如何用ATDD在更高的層面上來(lái)驅(qū)動(dòng)開(kāi)發(fā)過(guò)程以滿足客戶需求,即“做正確的事情”。在這以前,先來(lái)討論我們這些開(kāi)發(fā)人員從測(cè)試先行的開(kāi)發(fā)方式中能獲得什么好處。1.2.3這對(duì)我有什么好處沒(méi)人會(huì)無(wú)緣無(wú)故地買輛新車,也沒(méi)有人會(huì)僅僅因?yàn)殚_(kāi)發(fā)方法夠“新”就去使用它。要學(xué)一種新的工作方法必須有充分的理由,例如可以提高工作效率。我們現(xiàn)在已經(jīng)知道使用TDD和ATDD可以幫我們提高代碼質(zhì)量,滿足客戶業(yè)務(wù)需求了。那么這種開(kāi)發(fā)方法會(huì)如何讓我們更享受工作呢?根據(jù)我個(gè)人的使用經(jīng)驗(yàn),TDD起碼有三大好處:我很少會(huì)接到技術(shù)支持電話,就算有,也不會(huì)為了找出問(wèn)題而花很長(zhǎng)時(shí)間調(diào)試;我對(duì)代碼的質(zhì)量很有信心;我有更多的時(shí)間提高自己的專業(yè)素養(yǎng)。下面來(lái)一一解釋這些好處。不再長(zhǎng)時(shí)間調(diào)試代碼直到現(xiàn)在,我還記得幾年前接手的一項(xiàng)開(kāi)發(fā)任務(wù)。公司內(nèi)部有一個(gè)專有文件格式解釋器,我需要修復(fù)文件解釋器中的一個(gè)缺陷。我閱讀了成百行的代碼,在程序的各個(gè)部分間頻繁跳轉(zhuǎn),最終才明白如何修復(fù)這個(gè)缺陷。當(dāng)時(shí)我修改了解釋器的部分設(shè)計(jì),以為既可以修復(fù)缺陷還能使代碼更容易理解(那時(shí)候我還沒(méi)有開(kāi)始使用TDD)。實(shí)現(xiàn)新設(shè)計(jì)并且通過(guò)編譯用去了幾個(gè)小時(shí)。我懷著由那靈巧的設(shè)計(jì)帶來(lái)的激動(dòng)心情打開(kāi)了終端窗口,在測(cè)試服務(wù)器上安裝了解釋器,然后發(fā)現(xiàn)它居然不能正常工作!我完全不知道原因。用調(diào)試器運(yùn)行代碼后還是不知道問(wèn)題所在。我當(dāng)時(shí)花費(fèi)了數(shù)小時(shí)時(shí)間用調(diào)試器跟蹤執(zhí)行代碼,最后才發(fā)現(xiàn)了問(wèn)題——是個(gè)極小的錯(cuò)誤。我筋疲力盡地離開(kāi)辦公室,心情很沮喪,責(zé)怪自己太粗心大意。后來(lái)我意識(shí)到這不是因?yàn)榇中?,而是完成任?wù)的過(guò)程存在問(wèn)題——步子跨得太大,導(dǎo)致注意力分散。如果能用TDD的方式寫代碼,把開(kāi)發(fā)過(guò)程分解為小的、集中的測(cè)試,想必我能立即發(fā)現(xiàn)那個(gè)代碼分支中的問(wèn)題。好像那恐怖的調(diào)試體驗(yàn)還不夠糟糕似的,墨菲定律3再次向我招了招手。不久我接到了客戶怒氣十足的電話,抱怨解釋器在生產(chǎn)環(huán)境中崩潰了。我在修改解釋器的設(shè)計(jì)時(shí)引入了至少一個(gè)嚴(yán)重缺陷。能找出更優(yōu)的代碼設(shè)計(jì)是一回事兒,而凌晨三點(diǎn)被憤怒的客戶經(jīng)理叫醒是另外一回事兒。(而這位經(jīng)理被一個(gè)更惱火的客戶叫醒。)3事情如果有變壞的可能,不管這種可能性有多小,它總會(huì)發(fā)生。比如你衣袋里有兩把鑰匙,一把是你房間的,一把是汽車的,如果你現(xiàn)在想拿出車鑰匙,拿出的往往是房間鑰匙。如果當(dāng)時(shí)使用了TDD,或至少寫了必要的測(cè)試,那個(gè)晚上我應(yīng)該可以多睡兩個(gè)小時(shí)的。這件事強(qiáng)烈地激發(fā)起了我寫測(cè)試的意愿,因?yàn)橥蝗灰庾R(shí)到我過(guò)高估計(jì)了自己在工作方面的能力。對(duì)自己完成的工作很有信心我們當(dāng)然都希望寫出的代碼沒(méi)有錯(cuò)誤。如果代碼中存在太多的問(wèn)題飯碗就會(huì)難保。而另一方面我們又希望盡早完成工作,若在寫代碼上用去了過(guò)多的時(shí)間,日子同樣也不會(huì)好過(guò)。因此,我們時(shí)常需要判斷當(dāng)前工作是否已經(jīng)完成以及何時(shí)開(kāi)始下項(xiàng)工作。此刻,讓我們打開(kāi)記憶的匣子?;貞涍^(guò)去某個(gè)寫代碼時(shí)的場(chǎng)景,用一分鐘回想起那個(gè)時(shí)刻。你是如何寫那段代碼的?事先在記事簿上做設(shè)計(jì)?一下寫完全部實(shí)現(xiàn)期望一次通過(guò),否則就全部推倒重來(lái)?在循環(huán)中找出過(guò)明顯的問(wèn)題嗎?首次編譯能通過(guò)嗎?你如何驗(yàn)證某段代碼能正常工作?專門為測(cè)試寫一個(gè)主函數(shù)?在用戶界面上點(diǎn)來(lái)點(diǎn)去確認(rèn)功能已經(jīng)實(shí)現(xiàn)?在測(cè)試中發(fā)現(xiàn)過(guò)問(wèn)題嗎?用調(diào)試器一步步跟蹤代碼執(zhí)行?費(fèi)盡周折只為修復(fù)一個(gè)很小的缺陷?調(diào)試代碼和寫代碼大體上哪個(gè)更費(fèi)時(shí)間?無(wú)論答案是什么,我希望你已經(jīng)基本回憶起那些讓你充滿信心的代碼是如何編寫出來(lái)的了。好,我來(lái)問(wèn)你個(gè)問(wèn)題。如果你寫出的代碼完美無(wú)瑕,會(huì)不會(huì)感覺(jué)十分自信?如果知道代碼正如文檔中描述的那樣正常工作,會(huì)不會(huì)感覺(jué)心情舒暢?我會(huì)的。如果你能很快地完成工作,同時(shí)保證代碼質(zhì)量沒(méi)問(wèn)題,這種感覺(jué)又如何?若能一直這樣工作,會(huì)不會(huì)很開(kāi)心?我不能保證你使用TDD后開(kāi)發(fā)出的軟件就能完全沒(méi)缺陷,因?yàn)樽罱K還是你寫代碼,如何避免缺陷取決于你。我能保證的是,在使用TDD后,你會(huì)對(duì)你的軟件很有信心,因?yàn)槟忝鞔_知道軟件在各種情況下會(huì)如何工作。這個(gè)方法對(duì)提高軟件內(nèi)部質(zhì)量同樣有效。這可以說(shuō)是個(gè)良性循環(huán)。測(cè)試越好,代碼質(zhì)量越高,對(duì)代碼的改動(dòng)也越有信心。對(duì)代碼改動(dòng)有了信心,能夠改動(dòng)的地方就越多。改動(dòng)越多,代碼的內(nèi)部質(zhì)量就越高,也更容易編寫測(cè)試。這顯然是件好事!有更多時(shí)間做其他事情TDD和ATDD并不會(huì)提高我們敲鍵盤的速度,但能有效縮短由于低效開(kāi)發(fā)方式(例如調(diào)試代碼,理解可讀性不高的代碼,或因?yàn)檎`解需求而返工)所浪費(fèi)的時(shí)間。隨著開(kāi)發(fā)一步步進(jìn)行測(cè)試會(huì)不斷累積,我們對(duì)代碼質(zhì)量也會(huì)更有信心。我們不再會(huì)懷疑代碼在不同情況下會(huì)有不同的執(zhí)行結(jié)果,也不會(huì)擔(dān)憂一些數(shù)據(jù)的組合會(huì)使程序停止工作,因此也不必一遍遍重復(fù)地做測(cè)試。對(duì)代碼越有信心,我們就能越快地開(kāi)始下一項(xiàng)工作。當(dāng)然,有時(shí)自信是盲目的,不過(guò)即便如此,也會(huì)比這點(diǎn)一下在那輸入點(diǎn)數(shù)據(jù)來(lái)確認(rèn)功能是否正常要強(qiáng)的多。TDD和ATDD不是“銀彈”4,但也可以算是分時(shí)系統(tǒng)以來(lái)最能提高軟件開(kāi)發(fā)效率的技術(shù)了。在下一節(jié),我們會(huì)詳細(xì)講解TDD,然后討論ATDD。4西方文化中用“銀彈”來(lái)形容最強(qiáng)一招、最有效的手段、最大關(guān)注度、王牌等?!幷咦⑾旅骈_(kāi)始吧。1.3正確地做事:TDDTDD是一種開(kāi)發(fā)和設(shè)計(jì)技術(shù),可以幫我們?cè)隽渴降貥?gòu)建系統(tǒng),也能保證代碼在任何時(shí)刻都不會(huì)錯(cuò)得離譜。測(cè)試正是我們小步前進(jìn)的方法。在本節(jié)中,我們將會(huì)學(xué)到TDD的工作原理,也會(huì)了解這種技術(shù)所帶來(lái)的好處。我們先從TDD周期開(kāi)始介紹,這是TDD的核心內(nèi)容。然后我們會(huì)解釋讓軟件從一開(kāi)始就能正常工作的意義。若要增量式地構(gòu)建整個(gè)系統(tǒng),我們必須能只為當(dāng)前做設(shè)計(jì),而非一開(kāi)始就設(shè)計(jì)完所有東西。我們會(huì)討論如何用TDD實(shí)現(xiàn)增量式地構(gòu)建系統(tǒng)。最后我們會(huì)討論如何運(yùn)用這種方法來(lái)保證軟件每天都很“健康”,每天都能正常工作。開(kāi)始吧。接下來(lái)要講解的是TDD的測(cè)試—編碼—重構(gòu)周期。1.3.1測(cè)試—編碼—重構(gòu)本章第一段曾經(jīng)提到TDD的原則十分簡(jiǎn)單:寫代碼只為修復(fù)失敗了的測(cè)試。換言之,就是首先寫測(cè)試,然后編碼讓測(cè)試通過(guò)。有些人會(huì)覺(jué)得這種方法和在學(xué)校學(xué)到的有些矛盾。學(xué)校教我們先做設(shè)計(jì),接著實(shí)現(xiàn),最后測(cè)試以找出代碼中的問(wèn)題。而TDD顛倒了整個(gè)過(guò)程,如圖1-3所示。測(cè)試先行,然后編碼,最后做設(shè)計(jì)。這種“設(shè)計(jì)后行”的思維方法不奇怪嗎?一點(diǎn)也不。這種設(shè)計(jì)有別于傳統(tǒng)的“設(shè)計(jì)—編碼—測(cè)試”過(guò)程中的設(shè)計(jì)。并且因?yàn)檫@兩種設(shè)計(jì)區(qū)別很大,這種事后設(shè)計(jì)的方法甚至有了自己的名字——重構(gòu),表示把當(dāng)前設(shè)計(jì)轉(zhuǎn)換成一個(gè)更佳設(shè)計(jì)的過(guò)程。改名后的TDD周期如圖1-4所示:測(cè)試—編碼—重構(gòu)。圖1-3TDD顛倒了傳統(tǒng)的設(shè)計(jì)—編碼—測(cè)試順序。我們會(huì)先測(cè)試,然后編碼,最后設(shè)計(jì)圖1-4讓我們測(cè)試驅(qū)動(dòng)開(kāi)發(fā)人員著迷的測(cè)試—編碼—重構(gòu)過(guò)程。這幅圖精確地描述了開(kāi)發(fā)的過(guò)程,很容易理解,看起來(lái)也很酷這小小的“測(cè)試—編碼—重構(gòu)”周期看似簡(jiǎn)單,其內(nèi)部卻蘊(yùn)藏著巨大的威力。它足以改變每個(gè)人的軟件開(kāi)發(fā)過(guò)程的質(zhì)量,從而提高整個(gè)團(tuán)隊(duì)、項(xiàng)目及組織的軟件開(kāi)發(fā)過(guò)程的質(zhì)量。紅—綠—重構(gòu)TDD周期,即添加測(cè)試、編寫代碼通過(guò)測(cè)試及修改設(shè)計(jì),也可以用“紅—綠—重構(gòu)”表示。這些顏色有什么含義?當(dāng)我們進(jìn)行TDD周期的第一步,添加一個(gè)測(cè)試時(shí),測(cè)試會(huì)失敗。這是因?yàn)檫@時(shí)候系統(tǒng)有問(wèn)題,它并不具有我們所期望的功能。在一些開(kāi)發(fā)環(huán)境中失敗的測(cè)試會(huì)顯示為一個(gè)紅條,因此第一步為“紅”。第二步是寫代碼通過(guò)測(cè)試。我們實(shí)現(xiàn)了系統(tǒng)應(yīng)該有的功能,所有都測(cè)試通過(guò)了,包括為功能添加的新測(cè)試以及已經(jīng)存在的測(cè)試。這時(shí)候紅條變成了綠條,所以稱為“綠”。周期的最后一步是重構(gòu),即改善現(xiàn)有代碼的設(shè)計(jì)。因?yàn)橹貥?gòu)只改變代碼內(nèi)部結(jié)構(gòu)而并不改變外在行為,所以所有測(cè)試仍舊通過(guò),還是“綠”。紅、綠、綠,紅、綠、重構(gòu),很上口,不是嗎?我們將會(huì)在第二章詳細(xì)討論TDD周期中的每一步,不過(guò)在這之前,我們會(huì)先大致了解如何做,以及為何做這三個(gè)步驟。然后我們會(huì)討論其內(nèi)在機(jī)理。先寫測(cè)試在TDD周期中的第一步中,我們會(huì)寫測(cè)試,實(shí)際上這并不只是寫測(cè)試而已,而是在做設(shè)計(jì)。我們是在設(shè)計(jì)API,即用來(lái)訪問(wèn)新功能的接口。編碼之前寫測(cè)試,我們會(huì)很自然地考慮新代碼的調(diào)用方式。這過(guò)程就像拼圖一樣。我們必須根據(jù)拼圖周圍的部分來(lái)選擇拼哪塊,如圖1-5所示。圖1-5若不試著使用,我們?cè)趺磿?huì)知道接口應(yīng)當(dāng)是什么樣子?測(cè)試先行的編碼方式會(huì)促使我們站在代碼用戶(開(kāi)發(fā)人員)的角度考慮,設(shè)計(jì)出更易用的API這并不是件容易的事。也許你從用戶界面專家那里了解到了設(shè)計(jì)用戶界面有多么重要。軟件內(nèi)部的設(shè)計(jì)又何嘗不是如此?我們這些開(kāi)發(fā)人員不就是代碼的用戶嗎?若從這種角度觀察代碼,我們的思路會(huì)發(fā)生很大變化。我時(shí)常呆望著一些第三方類庫(kù)的API,思考著該如何使用它們。這些類庫(kù)的開(kāi)發(fā)人員肯定是站在開(kāi)發(fā)者的角度設(shè)計(jì)接口的,完全沒(méi)從用戶的角度考慮。接口是好是壞用過(guò)才會(huì)知道。這絕對(duì)是真理。若能測(cè)試先行地開(kāi)發(fā),我們肯定會(huì)對(duì)類庫(kù)的好壞有所體驗(yàn)。注意寫測(cè)試時(shí)應(yīng)當(dāng)注意粒度。應(yīng)當(dāng)盡量寫“正好足以失敗”的測(cè)試,而不是一下寫出整個(gè)功能點(diǎn)的測(cè)試然后花一個(gè)小時(shí)寫代碼讓測(cè)試通過(guò)。問(wèn)題領(lǐng)域、工具或技術(shù)都會(huì)影響編寫測(cè)試用的時(shí)間,不過(guò)通常編寫測(cè)試的時(shí)間都在幾秒鐘到幾分鐘之間。編碼的時(shí)間也應(yīng)如此。如果用的技術(shù)較復(fù)雜,那么寫測(cè)試及編碼的時(shí)間可能會(huì)變長(zhǎng)。不過(guò)在第二部分我們將會(huì)提到那些所謂復(fù)雜的技術(shù)(例如JavaServlet或數(shù)據(jù)訪問(wèn)的代碼)實(shí)際并不那么復(fù)雜。設(shè)計(jì)簡(jiǎn)單易用的API并不容易,因此我們要善于借助工具。用測(cè)試來(lái)驅(qū)動(dòng)設(shè)計(jì)就是行之有效的辦法,用這種方法能設(shè)計(jì)出模塊化并且可測(cè)試的代碼。因?yàn)橐獪y(cè)試先行,所以我們必須要讓代碼可以測(cè)試,沒(méi)有任何商量余地。在TDD中代碼都應(yīng)當(dāng)是可測(cè)試的,否則根本不會(huì)存在!設(shè)計(jì)軟件并不是只強(qiáng)調(diào)結(jié)構(gòu),滿足當(dāng)前的需要更加重要。一個(gè)會(huì)燒水、煮飯、炸薯片,還會(huì)做腌雞的軟件對(duì)一個(gè)只想喝杯茶的用戶一點(diǎn)用都沒(méi)有。如果你的汽車引擎多出來(lái)兩個(gè)閥門或許沒(méi)什么,因?yàn)樾枰~外動(dòng)力時(shí)這些閥門或許能幫上忙。不過(guò)若要更換引擎中的所有閥門那麻煩可就大了。這就是過(guò)度設(shè)計(jì)的代價(jià)?;ㄥX開(kāi)發(fā)并不需要的東西,還要為額外的復(fù)雜性買單。如果這項(xiàng)功能目前尚不需要,為什么要開(kāi)發(fā)呢?不如先將這項(xiàng)功能記在備忘錄上好了。有些功能完全有可能永遠(yuǎn)不用實(shí)現(xiàn)。使用TDD做開(kāi)發(fā),你會(huì)明確地知道軟件現(xiàn)在需要有什么功能。不是明天也不是昨天,就是現(xiàn)在。小步前進(jìn),寫代碼只為通過(guò)測(cè)試,我們就可以牢牢地控制住軟件及其設(shè)計(jì)。有了自動(dòng)化測(cè)試做保護(hù)傘,我們不再會(huì)誤入歧途,而且會(huì)很清楚前進(jìn)的方向,同時(shí)也能夠確信所實(shí)現(xiàn)的功能正是用戶所需要的。“強(qiáng)調(diào)現(xiàn)在”正是TDD的核心。這核心思想會(huì)在TDD周期的第二步再次得到體現(xiàn),即寫恰巧足夠的代碼。寫恰好足夠的代碼編寫恰好能通過(guò)測(cè)試的代碼是TDD周期的第二步。為什么恰巧夠就可以?新添加的測(cè)試之所以會(huì)失敗,是因?yàn)樗赋隽讼到y(tǒng)應(yīng)該有但尚未實(shí)現(xiàn)的功能。我們應(yīng)該只用花幾分鐘就能實(shí)現(xiàn)這項(xiàng)功能,測(cè)試失敗的狀態(tài)不應(yīng)持續(xù)太長(zhǎng)時(shí)間。讓測(cè)試指出下一步該做的事情是TDD的基本理念。我們不僅僅是在制造代碼,而是在實(shí)現(xiàn)一項(xiàng)具體的功能,而測(cè)試能毫無(wú)歧義地描述出這項(xiàng)功能。每次測(cè)試通過(guò),我們都清楚地知道工作有了進(jìn)展。注意,寫恰巧足夠的代碼是為了讓測(cè)試盡快通過(guò)。因此當(dāng)前的實(shí)現(xiàn)方式或許不是最優(yōu)的。不過(guò)沒(méi)關(guān)系,等功能實(shí)現(xiàn)、測(cè)試通過(guò)后,我們會(huì)回來(lái)解決這個(gè)問(wèn)題的。有了測(cè)試做保護(hù)傘,就可以進(jìn)行TDD周期中的最后一步——重構(gòu)了。重構(gòu)重構(gòu)是TDD測(cè)試—編碼—重構(gòu)周期的最后一步。我們回過(guò)頭審視現(xiàn)有的代碼設(shè)計(jì),想辦法改進(jìn)。重構(gòu)使TDD步伐更加穩(wěn)健。使用TDD而不重構(gòu)能迅速產(chǎn)生大量的爛代碼。無(wú)論有多么充足的測(cè)試,爛代碼終歸還是爛代碼。優(yōu)良的代碼質(zhì)量能保證今后的開(kāi)發(fā)效率,所以重構(gòu)必不可少。這一步至關(guān)重要,所以我們需要用一整節(jié)詳細(xì)討論。在這之前,我們先對(duì)小步地增量開(kāi)發(fā)軟件的方式做個(gè)概覽吧。1.3.2增量式開(kāi)發(fā)所有敏捷軟件開(kāi)發(fā)過(guò)程都有一個(gè)共同點(diǎn),即無(wú)論當(dāng)前的功能有多么少,都要保證軟件可隨時(shí)發(fā)布,并且每天都要能持續(xù)產(chǎn)生可部署的版本(有些項(xiàng)目甚至每天都會(huì)構(gòu)建出幾個(gè)可發(fā)布的版本,直到項(xiàng)目完成)。這樣,當(dāng)最后發(fā)布期限來(lái)臨時(shí)至少有可用的產(chǎn)品能發(fā)布。雖然產(chǎn)品或許不包括用戶想要的所有功能,團(tuán)隊(duì)也可能沒(méi)有完成迭代計(jì)劃,但是有產(chǎn)品可以發(fā)布總比沒(méi)有強(qiáng),更何況這產(chǎn)品運(yùn)轉(zhuǎn)良好。在圖1-6描繪的過(guò)程中,已完成且測(cè)試過(guò)的功能不斷地累積。在每個(gè)時(shí)間點(diǎn)上,都只有少量的功能尚未完成或集成。很多項(xiàng)目會(huì)不斷地推遲交付時(shí)間,最后整個(gè)項(xiàng)目都會(huì)被取消,一行代碼都未能交付。通過(guò)迭代式的小的增量式開(kāi)發(fā),你完全不用擔(dān)心這個(gè)問(wèn)題,因?yàn)閺牡谝粋€(gè)迭代起軟件就是可交付的。同樣,很多的項(xiàng)目在交付時(shí)質(zhì)量不過(guò)關(guān),因?yàn)橹钡阶詈笠豢涕_(kāi)發(fā)人員還在趕工。TDD可以解決這個(gè)問(wèn)題。在TDD過(guò)程中,我們會(huì)小步地前進(jìn),每一小步都會(huì)產(chǎn)生一個(gè)工作良好的產(chǎn)品,每一步都會(huì)距離目標(biāo)更近一些。這些步驟非常的小(以分鐘計(jì)算,而非小時(shí)或天),我們完全不用擔(dān)心剛寫出來(lái)的一大堆代碼拼在一起不能工作。我們從不讓軟件偏離可用狀態(tài)太遠(yuǎn),因此軟件一直都能正常工作。同樣,我們會(huì)著眼于現(xiàn)在而不是預(yù)測(cè)將來(lái),這樣就能保持軟件精簡(jiǎn)實(shí)用了。圖1-6使用增量式開(kāi)發(fā),即以小的增量方式構(gòu)建整個(gè)系統(tǒng),我們的代碼永遠(yuǎn)不會(huì)距離已集成的、可工作的狀態(tài)太遠(yuǎn)。這樣可以降低風(fēng)險(xiǎn),因?yàn)槲赐瓿傻墓ぷ饕恢倍己苌佟N覀円院髮?huì)學(xué)到,使用增量式開(kāi)發(fā),用戶總能夠看到真實(shí)的、可工作的軟件,客戶也能不斷提出反饋,因此團(tuán)隊(duì)的工作也會(huì)更加高效傳統(tǒng)過(guò)程強(qiáng)調(diào)“事先設(shè)計(jì)所有東西,考慮所有的風(fēng)險(xiǎn),這樣架構(gòu)才可以經(jīng)得住考驗(yàn),將來(lái)才能支撐起所有系統(tǒng)特性”。若采用這種方式,那我們是無(wú)法做到增量式構(gòu)建軟件,特別是依照成本和業(yè)務(wù)價(jià)值來(lái)增量式地構(gòu)建軟件的。傳統(tǒng)的方式只能用于極其簡(jiǎn)單,或者每個(gè)細(xì)節(jié)都已經(jīng)被透徹理解了的項(xiàng)目。而其他類型的項(xiàng)目則需要用迭代的辦法一步步增量式設(shè)計(jì)。如圖1-7所示,在迭代、增量式的開(kāi)發(fā)過(guò)程中,我們需要不斷地在“實(shí)現(xiàn)新功能”和為支持新功能而“調(diào)整設(shè)計(jì)乃至架構(gòu)”兩項(xiàng)任務(wù)間來(lái)回切換。圖1-7增量式演化設(shè)計(jì)是指在系統(tǒng)不斷添加更多的功能和行為的過(guò)程中,不斷地微調(diào)代碼結(jié)構(gòu)。在代碼生命周期的任何時(shí)刻,代碼所展現(xiàn)的都是開(kāi)發(fā)人員為完成現(xiàn)有功能所做的最好的設(shè)計(jì)。用這種方法,我們可以演化出能經(jīng)受實(shí)踐檢驗(yàn)的架構(gòu)這就是增量式演化設(shè)計(jì)。我們只為完成當(dāng)前功能而設(shè)計(jì),而不會(huì)試圖事先做完所有設(shè)計(jì)。按照開(kāi)發(fā)過(guò)程中獲得的信息調(diào)整當(dāng)前設(shè)計(jì),而不是在項(xiàng)目一開(kāi)始的所謂設(shè)計(jì)階段就企圖預(yù)見(jiàn)到所有應(yīng)用場(chǎng)景,然后基于這些或?qū)嵒蛱摰募僭O(shè)做設(shè)計(jì)。事先設(shè)計(jì)的量要依情況而定,需要考慮當(dāng)前團(tuán)隊(duì)、個(gè)體,以及所采用的技術(shù)等因素。我們要時(shí)刻注意保持正確的方向,所做的設(shè)計(jì)大部分都不對(duì)?那就少做點(diǎn)事先設(shè)計(jì)。發(fā)現(xiàn)設(shè)計(jì)不容易擴(kuò)展?那就多做些事先設(shè)計(jì)。我們?cè)谖闹谐3L岬健靶〔襟E”這個(gè)詞,下面來(lái)解釋一下其優(yōu)點(diǎn)吧。小到能夠裝進(jìn)我們的腦袋有兩個(gè)原因促使我們把大的任務(wù)分解成許多小任務(wù)。首先,我們要解決的問(wèn)題通常都比較大而且模糊,其復(fù)雜性也不易控制,因此我們需要把它分解成更容易解決的小問(wèn)題。不知道你是否和我一樣,至少我的腦袋不太擅長(zhǎng)對(duì)付這種大的怪物。圖1-8演示了如何把復(fù)雜的問(wèn)題拆分成更簡(jiǎn)單的小問(wèn)題,然后一個(gè)個(gè)解決。圖1-8要解決復(fù)雜的問(wèn)題,先集中解決其中一小部分效果會(huì)更好大部分人的工作記憶都只能同時(shí)處理5到7個(gè)概念。如果一次處理的信息太多,大腦就必須在工作記憶和長(zhǎng)期記憶之間來(lái)回切換,因此難免遺漏部分信息1。若能把整個(gè)解決問(wèn)題的過(guò)程分解成一個(gè)個(gè)小的階段性目標(biāo),整體進(jìn)度也就有了具體的衡量標(biāo)準(zhǔn)。在開(kāi)發(fā)過(guò)程中,由于整個(gè)過(guò)程劃分成了一系列的測(cè)試,利用這些測(cè)試,我們完全可以度量出當(dāng)前的開(kāi)發(fā)進(jìn)度。1工作記憶就像內(nèi)存,而長(zhǎng)期記憶類似于外部存儲(chǔ),例如硬盤。內(nèi)存速度比硬盤快幾個(gè)數(shù)量級(jí),因此若計(jì)算所用數(shù)據(jù)都在內(nèi)存里,那么運(yùn)算速度也會(huì)快很多。有興趣的讀者可以參閱科學(xué)出版社引進(jìn)的《人腦功能》?!g者注在TDD過(guò)程中我們會(huì)演進(jìn)式地設(shè)計(jì)系統(tǒng),所以正好是小步地前進(jìn)。我們會(huì)持續(xù)地改良系統(tǒng)設(shè)計(jì),在開(kāi)發(fā)過(guò)程中逐漸地演化出最適合當(dāng)前需求的架構(gòu)。現(xiàn)在我們來(lái)仔細(xì)研究演進(jìn)式設(shè)計(jì)為何行之有效,為何能讓我們的代碼“活”起來(lái),以及對(duì)我們的工作帶來(lái)的影響。演進(jìn)式設(shè)計(jì)很多程序員可能都遇見(jiàn)過(guò)這種事:某塊代碼亟待修改,卻沒(méi)有人愿意接手。為什么會(huì)這樣?這段代碼正巧是兩個(gè)組件間的接口,修改工作太過(guò)困難。而在演進(jìn)式設(shè)計(jì)中,我們常常會(huì)做這種修改。代碼應(yīng)當(dāng)是“活的”并且是“可生長(zhǎng)”的,決不能無(wú)視強(qiáng)烈的變化需求而保持一成不變。正因?yàn)槿绱?,演進(jìn)式設(shè)計(jì)可以提高設(shè)計(jì)質(zhì)量,進(jìn)而提高整個(gè)系統(tǒng)的質(zhì)量。那么,究竟該如何做演進(jìn)式設(shè)計(jì)呢?演進(jìn)式設(shè)計(jì)也是小步前進(jìn)的。每種敏捷軟件過(guò)程中推薦的事先設(shè)計(jì)的量都不同,不過(guò)這些過(guò)程都有一個(gè)共同點(diǎn),即要求架構(gòu)恰好能滿足需要即可。例如你在開(kāi)發(fā)某個(gè)軟件,到了中期時(shí)遇到一個(gè)需求,要用到郵件服務(wù)器,直到這時(shí)候你才會(huì)往架構(gòu)中加入郵件服務(wù)的構(gòu)件,才會(huì)安裝郵件服務(wù)器,等等。通常這種類型的架構(gòu)改動(dòng)很容易,但是并不是所有修改都是這樣。軟件系統(tǒng)會(huì)有些非功能性需求,例如性能等,修改現(xiàn)有架構(gòu)支持這類需求通常很不容易。例如把一個(gè)單機(jī)版的桌面應(yīng)用程序改成能通過(guò)網(wǎng)絡(luò)與多用戶服務(wù)器通信的桌面客戶端,工作量可不會(huì)小。同樣,讓一個(gè)批處理應(yīng)用程序支持實(shí)時(shí)自動(dòng)更新也不是件輕松的任務(wù)。不過(guò)這類需求不會(huì)讓開(kāi)發(fā)人員多驚訝。雖然我們通常難以預(yù)測(cè)需求變化,但是根據(jù)現(xiàn)有需求預(yù)見(jiàn)將來(lái)的需求變化并不是不可能的。我們將這類變化稱為可預(yù)見(jiàn)的變化,如圖1-9所示。不過(guò),可預(yù)見(jiàn)的變化并不一定會(huì)發(fā)生,有時(shí)甚至永遠(yuǎn)不會(huì)發(fā)生,還有的會(huì)因?yàn)槟撤N原因而轉(zhuǎn)化為不可預(yù)見(jiàn)的變化。圖1-9系統(tǒng)的演進(jìn)式設(shè)計(jì)同時(shí)受“可預(yù)見(jiàn)”和“不可預(yù)見(jiàn)”兩種變化的影響。不過(guò)值得注意的是,相當(dāng)一部分“可預(yù)見(jiàn)”的變化都不會(huì)發(fā)生,或者以某種不曾預(yù)料到的方式出現(xiàn),這時(shí)它們就變成了“不可預(yù)見(jiàn)”的變化。我們需要運(yùn)用常識(shí)和敏銳的判斷力,為可能發(fā)生的變化作準(zhǔn)備。大部分這種變化或許永遠(yuǎn)不會(huì)發(fā)生做演進(jìn)設(shè)計(jì)時(shí)我們一樣要使用常識(shí),不過(guò)要明白事情是會(huì)發(fā)生變化的,同時(shí)要注意現(xiàn)有任務(wù)的優(yōu)先級(jí)。例如我們知道系統(tǒng)目前會(huì)通過(guò)網(wǎng)絡(luò)從公司的CRM系統(tǒng)中更新信息,不過(guò)在幾個(gè)月后更新過(guò)程會(huì)變?yōu)閷?shí)時(shí)的,而且會(huì)用HTTP協(xié)議調(diào)用Web服務(wù)。在這種情況下,我們?cè)撊绾螒?yīng)對(duì)實(shí)時(shí)數(shù)據(jù)集成帶來(lái)的新需求呢?我們應(yīng)當(dāng)把數(shù)據(jù)處理邏輯和數(shù)據(jù)接收邏輯分開(kāi)嗎?當(dāng)然!那我們應(yīng)當(dāng)現(xiàn)在就開(kāi)始做一個(gè)基于批處理的系統(tǒng),等Web服務(wù)上線后直接就能投入使用嗎?或許應(yīng)該,或許不應(yīng)該??傊覀冃枰凇氨苊庾鰺o(wú)用功”和“先挑省事的方法做,等出了問(wèn)題再說(shuō)”之間做出權(quán)衡。實(shí)踐一次又一次地證明,比起紙上談兵的事先設(shè)計(jì),演進(jìn)式設(shè)計(jì)方法更加經(jīng)濟(jì)有效。遵守紀(jì)律再?gòu)?qiáng)調(diào)一遍,每個(gè)項(xiàng)目要做的事先設(shè)計(jì)的量都不一樣(原本就不該一刀切),但是在演進(jìn)式設(shè)計(jì)中,我們總會(huì)為添加新需求而修改大量現(xiàn)有的代碼。這種改動(dòng)相當(dāng)頻繁,所以代碼質(zhì)量一定要高。為此我們需要不少嚴(yán)格的規(guī)章紀(jì)律,確保開(kāi)發(fā)人員不會(huì)降低代碼質(zhì)量2。幸好,演進(jìn)式設(shè)計(jì)及其輔助實(shí)踐可以幫我們解決這個(gè)問(wèn)題。它能幫我們把缺陷數(shù)量降低到接近零的水平3,而不會(huì)讓整個(gè)系統(tǒng)構(gòu)建在大堆隱藏的缺陷的基礎(chǔ)上。2\h/resources/articles/Principles_and_Patterns.pdf。3我并不打算證明零缺陷是可能的,你可以隨意發(fā)表你的意見(jiàn)。這些輔助性的實(shí)踐究竟是什么?簡(jiǎn)言之,這些實(shí)踐都是為了保證軟件的質(zhì)量在任何時(shí)候都足夠好,而重構(gòu)是其中很重要的一環(huán)。我們前面提到過(guò)重構(gòu)是TDD周期,即測(cè)試—編碼—重構(gòu)的最后一步。下面我們會(huì)詳細(xì)講解重構(gòu)的重要之處。1.3.3重構(gòu)以保持代碼的健康在小步前進(jìn)的過(guò)程中,我們會(huì)不斷擴(kuò)展當(dāng)前設(shè)計(jì)以支持新功能,也會(huì)不斷拋棄舊概念,引進(jìn)新概念。在這種情況下,軟件的設(shè)計(jì)必然會(huì)變得很不一致,幾經(jīng)失衡,不易理解,也難以擴(kuò)展。這些肯定會(huì)給我們交付軟件的能力帶來(lái)負(fù)面影響。不過(guò)不用擔(dān)心,通過(guò)做重構(gòu),我們既可以演進(jìn)式地設(shè)計(jì),又能保持系統(tǒng)設(shè)計(jì)不出問(wèn)題。來(lái)看看MartinFowler在他的著作《重構(gòu):改善既有代碼的設(shè)計(jì)》4中對(duì)重構(gòu)的定義:“重構(gòu)是一種訓(xùn)練有素、有條不紊的代碼清理方式,它必須在不改變代碼外在行為的情況下,改進(jìn)程序的內(nèi)部結(jié)構(gòu)?!边@句話雖然很短,但卻傳達(dá)了很多的信息,下面我們來(lái)詳細(xì)解釋這句話的含義。4本書已由人民郵電出版社出版?!幷咦⒅貥?gòu)是種紀(jì)律嚴(yán)明的方法當(dāng)重構(gòu)(動(dòng)詞)時(shí),我們并不僅僅是修改代碼而已,而是通過(guò)一種嚴(yán)格的方式改善現(xiàn)有設(shè)計(jì)。這種方式叫作重構(gòu)(名詞),它能小步地改變代碼結(jié)構(gòu)同時(shí)不改變代碼的行為。換言之,重構(gòu)是指用某種嚴(yán)格的方式重構(gòu)(動(dòng)詞)代碼。重構(gòu)或許會(huì)顯著地改變當(dāng)前設(shè)計(jì),不過(guò)這種改變總是小步進(jìn)行的,而且每一步都會(huì)驗(yàn)證代碼的行為沒(méi)有改變。我們可不僅僅是在修改代碼。我們要首先確定設(shè)計(jì)中的問(wèn)題,然后選擇對(duì)應(yīng)的重構(gòu)方法小心徹底地重構(gòu)這段代碼。我們會(huì)靜候問(wèn)題浮出水面,然后再解決問(wèn)題。我們并不會(huì)預(yù)測(cè)設(shè)計(jì)可能出現(xiàn)的問(wèn)題,也不會(huì)為這種問(wèn)題作準(zhǔn)備——因?yàn)檫@不會(huì)使問(wèn)題減少,反而會(huì)讓事情變得更糟。重構(gòu)是種轉(zhuǎn)換過(guò)程重構(gòu)是兩種狀態(tài)間的轉(zhuǎn)換。在初始狀態(tài)中,代碼的設(shè)計(jì)存在一定問(wèn)題,而在目標(biāo)狀態(tài)中這些問(wèn)題都已經(jīng)被修復(fù)了。圖1-10中的重構(gòu)手法為“以委托取代繼承”(請(qǐng)參考MartinFowler的《重構(gòu):改善既有代碼的設(shè)計(jì)》一書)。這種重構(gòu)手法的目的在于把繼承轉(zhuǎn)化成委托。若子類只想重用父類一小部分功能,但卻繼承了父類大部分我們并不需要的數(shù)據(jù)和功能,這時(shí)候就可以應(yīng)用重構(gòu)這種手法。有些重構(gòu)方法非常成熟,對(duì)很多現(xiàn)代開(kāi)發(fā)工具都有很好的支持。把重構(gòu)過(guò)程自動(dòng)化使得用重構(gòu)做演進(jìn)式設(shè)計(jì)更容易了,無(wú)論項(xiàng)目的規(guī)模有多大或復(fù)雜性有多高。(很難想象僅僅為了給一個(gè)方法重命名而去搜索幾十個(gè)源代碼文件,用查找替換功能一處一處改名,然后再把改動(dòng)提交到版本控制系統(tǒng)中去。)圖1-10重構(gòu)是指代碼在功能完全相同的兩個(gè)狀態(tài)或結(jié)構(gòu)之間的轉(zhuǎn)換。在圖中,我們用委托替代了繼承。轉(zhuǎn)換后代碼功能不變,但卻更符合我們當(dāng)前的設(shè)計(jì)需要。這些轉(zhuǎn)換不是絕對(duì)的改進(jìn),而僅僅是用戶兩種設(shè)計(jì)方案互相轉(zhuǎn)換的“紀(jì)律嚴(yán)明”的方法而已。很多重構(gòu)手法都有其反向重構(gòu)手法,重構(gòu)的方向正好相反重構(gòu)到模式有時(shí)候重構(gòu)的起始狀態(tài)或結(jié)束狀態(tài)可能會(huì)對(duì)應(yīng)某個(gè)設(shè)計(jì)模式5。所謂設(shè)計(jì)模式就是對(duì)特定問(wèn)題的通用解決辦法。雖然我們?nèi)粘5闹貥?gòu)動(dòng)作都很小,并沒(méi)有像使用設(shè)計(jì)模式那樣會(huì)改動(dòng)大量的代碼,不過(guò)重構(gòu)過(guò)程中也需要發(fā)掘代碼中可能的設(shè)計(jì)模式,然后顯式地重構(gòu)出模式。要深入了解重構(gòu)和模式之間的關(guān)系,請(qǐng)參閱JoshuaKerievsky所著的《重構(gòu)與模式》6。5ParthaKuchana編著的SoftwareArchitectureDesignPatternsinJava(Auerbach,2004)是介紹Java語(yǔ)言實(shí)現(xiàn)的經(jīng)典設(shè)計(jì)模式的好書。6人民郵電出版社出版?!幷咦⒅貥?gòu)會(huì)改變內(nèi)部結(jié)構(gòu)重構(gòu)用于改變系統(tǒng)內(nèi)部結(jié)構(gòu)——代碼,因此大部分重構(gòu)手法都會(huì)涉及實(shí)現(xiàn)細(xì)節(jié),例如應(yīng)用最為頻繁的重構(gòu)手法“重命名”。給方法或者局部變量重命名對(duì)系統(tǒng)的設(shè)計(jì)貌似不會(huì)有太大影響,但是若把某個(gè)含義模糊的名字改得能清晰地表達(dá)出本意,那么對(duì)閱讀代碼的人就會(huì)輕松多了。這些小的重構(gòu)是更大的重構(gòu)的基礎(chǔ)。在“大重構(gòu)”中,我們通常會(huì)改變代碼的職責(zé),引入或移除繼承結(jié)構(gòu),或者做一些會(huì)涉及幾個(gè)類的類似操作等。最終大重構(gòu)都可以分解為一系列小步驟,例如給變量重命名,添加一個(gè)新的類,改變方法返回值,等等。雖然從技術(shù)角度來(lái)看“重命名”等重構(gòu)方法使用率最高,但重構(gòu)最主要的卻是為了消除重復(fù)。重復(fù)是指兩個(gè)方法或類中有相似的代碼。我們可以把重復(fù)的代碼提取到公共方法中,然后再調(diào)用這個(gè)方法來(lái)消除重復(fù)。若代碼中存在重復(fù),表明代碼職責(zé)劃分得不夠清楚。重復(fù)對(duì)系統(tǒng)當(dāng)然是有害的,因?yàn)樗瓜到y(tǒng)變動(dòng)更麻煩。讓邏輯和職責(zé)在不同地方重復(fù)一定會(huì)引入缺陷,因?yàn)槲覀兒苋菀仔薷囊惶幎浧渌?。重?gòu)時(shí)不僅不能引入缺陷,也不能添加新功能。重構(gòu)應(yīng)當(dāng)保持系統(tǒng)原有行為。重構(gòu)保持原有行為MartinFowler提到重構(gòu)應(yīng)該“不改變代碼的外部行為”,這是什么意思?這是指無(wú)論怎么修改代碼,所變化的都只有代碼的設(shè)計(jì)和內(nèi)部結(jié)構(gòu),外部可見(jiàn)的行為和功能會(huì)維持原樣。換言之,代碼的用戶應(yīng)當(dāng)對(duì)重構(gòu)毫無(wú)察覺(jué)。改變一個(gè)類的公共方法的名字,肯定會(huì)影響到調(diào)用這個(gè)方法的代碼,但是這個(gè)方法的行為不應(yīng)該發(fā)生改變。同樣,調(diào)整公共接口背后的代碼也不會(huì)改變接口提供的功能。注意現(xiàn)在我們已經(jīng)知道了什么是重構(gòu),不過(guò),知道什么不是重構(gòu)也很重要。這些年我常聽(tīng)到“重構(gòu)”這個(gè)詞,不過(guò)在大部分情況下,人們只是想表達(dá)“重寫”或“編輯”而已。重構(gòu)時(shí)只能改變系統(tǒng)內(nèi)部結(jié)構(gòu)而不能改變系統(tǒng)外部行為,對(duì)嗎?那我們?cè)趺粗勒娴臎](méi)有改變系統(tǒng)的外部行為呢?當(dāng)然是靠測(cè)試了!測(cè)試可以確保軟件正常運(yùn)行。1.3.4保證軟件正常運(yùn)行保證從項(xiàng)目第一天起就能交付軟件,說(shuō)起來(lái)容易做起來(lái)難。既要不斷重構(gòu)代碼,又要保證重構(gòu)工作不破壞已有的功能,可不是件容易的事。在無(wú)人督促時(shí),我們有可能會(huì)偷懶。雖然TDD中測(cè)試的主要目的是幫助我們?cè)O(shè)計(jì)和開(kāi)發(fā)軟件,不過(guò)這種方法也能督促我們堅(jiān)持正確的做事方式。測(cè)試并不是負(fù)擔(dān),不過(guò)許多人都不這么認(rèn)為7。手工測(cè)試的確很慢,也容易出錯(cuò),因此開(kāi)發(fā)人員(甚至專業(yè)測(cè)試人員)都不喜歡做測(cè)試,總會(huì)跳過(guò)某些手工測(cè)試,猜想待測(cè)功能不會(huì)有問(wèn)題。不過(guò)手工測(cè)試的缺點(diǎn)是很容易克服的,把手工勞動(dòng)轉(zhuǎn)化為自動(dòng)化形式就是我們軟件開(kāi)發(fā)人員的本職工作,因此把手工測(cè)試變成自動(dòng)化測(cè)試完全難不倒我們!7實(shí)際上測(cè)試先行地開(kāi)發(fā)時(shí),寫測(cè)試是種樂(lè)趣!用自動(dòng)化測(cè)試做保護(hù)回歸是指返回到更初級(jí)的狀態(tài)。在軟件開(kāi)發(fā)領(lǐng)域,回歸表明已有的、曾經(jīng)可用的功能不再能正常運(yùn)轉(zhuǎn),即從正常工作的狀態(tài)退化到不能工作的狀態(tài),如圖1-11所示。圖1-11測(cè)試套件在代碼周圍形成了一個(gè)模子。若改動(dòng)破壞了功能,模子就不再符合了。若模子破損了,我們也就知道不好的事情發(fā)生了回歸不會(huì)平白無(wú)故地發(fā)生。實(shí)際上,是我們開(kāi)發(fā)人員自己修改代碼時(shí)引入了缺陷才會(huì)出現(xiàn)回歸。作為專業(yè)軟件開(kāi)發(fā)人員,我們當(dāng)然希望能迅速知道當(dāng)前改動(dòng)是否會(huì)破壞現(xiàn)有功能,因此必須依靠測(cè)試套件。我們可以隨時(shí)執(zhí)行所有測(cè)試,若測(cè)試失敗了,就表明某項(xiàng)功能出現(xiàn)了問(wèn)題。這種測(cè)試稱為回歸測(cè)試,在整個(gè)開(kāi)發(fā)過(guò)程中,回歸測(cè)試會(huì)被反復(fù)執(zhí)行許多遍,以保證以前發(fā)現(xiàn)的缺陷不會(huì)在系統(tǒng)發(fā)生上千次修改及變化后再次出現(xiàn)。換言之,這種測(cè)試是為了保證自上次運(yùn)行測(cè)試以來(lái),軟件沒(méi)有發(fā)生回歸。測(cè)試套件有許多重要特征,首先其必須能容易運(yùn)行——否則人們就會(huì)試圖繞過(guò)測(cè)試,冒著破壞現(xiàn)有功能的風(fēng)險(xiǎn)提交代碼。另外還要能快速地執(zhí)行——否則人們就不會(huì)常常執(zhí)行測(cè)試,因此又會(huì)跳過(guò)很重要的功能驗(yàn)證步驟。若不頻繁地執(zhí)行測(cè)試,那么將來(lái)我們可能花費(fèi)很多寶貴的時(shí)間修復(fù)缺陷,因?yàn)槟菚r(shí)完全不知道缺陷是在哪一步被引入的。及時(shí)反饋所帶來(lái)的正確上下文,可以幫我們明顯提高開(kāi)發(fā)速度,因?yàn)椴恍枰〞r(shí)間找出引入缺陷時(shí)的上下文。如果我們?cè)诿看挝⑿∽儎?dòng)后都運(yùn)行自動(dòng)化測(cè)試,那么我們可以精確地知道出錯(cuò)的是哪幾行代碼??焖佾@得反饋有時(shí)我們沒(méi)法很快地執(zhí)行完所有測(cè)試。那些訪問(wèn)數(shù)據(jù)庫(kù)、共享磁盤、Internet上的Web服務(wù)器或者目錄服務(wù)的測(cè)試會(huì)讓測(cè)試速度變得很慢,甚至連訪問(wèn)本地文件系統(tǒng)都會(huì)使整個(gè)測(cè)試多運(yùn)行數(shù)分鐘時(shí)間。而且有時(shí)候測(cè)試次數(shù)太多,就算優(yōu)化很多次依舊會(huì)占用開(kāi)發(fā)人員大量的時(shí)間。在這種情況下,可以選擇運(yùn)行一部分測(cè)試(通常是最可能發(fā)現(xiàn)當(dāng)前改動(dòng)所引發(fā)缺陷的那部分測(cè)試),提交代碼,繼續(xù)下一項(xiàng)工作,讓構(gòu)建服務(wù)器(buildserver)在后臺(tái)運(yùn)行所有測(cè)試。構(gòu)建服務(wù)器有時(shí)稱為持續(xù)集成服務(wù)器8,因?yàn)樗?jīng)常和“持續(xù)集成”一起使用。所謂持續(xù)集成是指開(kāi)發(fā)人員頻繁地集成修改過(guò)的代碼,使得集成幾乎是“持續(xù)”的。8MartinFowler的文章\h/articles/continuousIntegration.html詳細(xì)地介紹了持續(xù)集成。擁有持續(xù)集成服務(wù)器并不等于做持續(xù)集成不應(yīng)該把擁有持續(xù)集成服務(wù)器與進(jìn)行持續(xù)集成等同起來(lái)。持續(xù)集成服務(wù)器可以使構(gòu)建過(guò)程自動(dòng)化,還能產(chǎn)生精美的報(bào)表。但如果開(kāi)發(fā)人員很少提交代碼,那么持續(xù)集成服務(wù)器就會(huì)形同虛設(shè),開(kāi)發(fā)人員的代碼也會(huì)變得不同步,集成時(shí)當(dāng)然會(huì)碰到更多的代碼沖突。這種方法基本上是用“上次修改代碼沒(méi)有破壞任何功能”的信心來(lái)交換開(kāi)發(fā)速度的,而這種交換則建立在開(kāi)發(fā)人員挑選的部分測(cè)試沒(méi)有覆蓋的功能不會(huì)被該修改破壞的假設(shè)之上。我們可以把這種方式想象為快速獲得反饋的“樂(lè)觀鎖”。如果測(cè)試沒(méi)有失敗,我們可以只關(guān)注于開(kāi)發(fā),完全不用花時(shí)間去運(yùn)行那些不會(huì)失敗的測(cè)試。如果讓構(gòu)建服務(wù)器運(yùn)行的測(cè)試失敗了,那我們需要用更多的時(shí)間找出問(wèn)題所在,因?yàn)槭种械娜蝿?wù)已經(jīng)換了,問(wèn)題發(fā)生時(shí)的上下文也改變了。我們需要做出取舍,因?yàn)槿魶](méi)有問(wèn)題發(fā)生的上下文,修復(fù)缺陷的時(shí)間和精力會(huì)大大地增加?;貧w測(cè)試必須要能夠重復(fù)執(zhí)行。也就是說(shuō)我們要么雇人用大量時(shí)間重復(fù)執(zhí)行成千上萬(wàn)的回歸測(cè)試,要么自動(dòng)化所有的測(cè)試,讓計(jì)算機(jī)來(lái)自動(dòng)地執(zhí)行回歸測(cè)試。我們現(xiàn)在已經(jīng)明白了TDD是什么,如何運(yùn)作,也明白了采用這種技術(shù)的原因。我們將會(huì)在第一及第二部分的剩下章節(jié)中詳細(xì)講解這種技術(shù),不過(guò)在這之前,我們會(huì)先了解驗(yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的基本功能,以及如何能幫我們開(kāi)發(fā)出滿足客戶需求的軟件。1.4做正確的事:ATDD測(cè)試一直都是軟件開(kāi)發(fā)過(guò)程中重要的一環(huán),測(cè)試的具體方法這些年發(fā)生了很大的變化。在把軟件交付給客戶之前,我們開(kāi)發(fā)人員都會(huì)想盡辦法確保交付的軟件沒(méi)有問(wèn)題。隨著敏捷軟件開(kāi)發(fā)(如XP)過(guò)程的逐漸流行,我們檢測(cè)軟件質(zhì)量的方法也發(fā)生了不少變化。測(cè)試曾經(jīng)只是為了驗(yàn)證軟件功能和需求文檔所描述的一致,不過(guò)現(xiàn)在測(cè)試的功能已經(jīng)不局限于此了。實(shí)際上,利用測(cè)試從功能的角度驅(qū)動(dòng)軟件開(kāi)發(fā)過(guò)程,正是我們解決軟件不能滿足客戶真正需求問(wèn)題的方法。這就是驗(yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(ATDD)的內(nèi)容。簡(jiǎn)言之,我們會(huì)先寫一個(gè)測(cè)試,然后再實(shí)現(xiàn)測(cè)試所描述的功能。因此,測(cè)試不僅僅是一種驗(yàn)證工具,還是需求文檔必不可少的一部分,同時(shí)也是與客戶協(xié)作的媒介。在本節(jié)中,我們會(huì)詳細(xì)討論測(cè)試的這些新用途,先從促進(jìn)開(kāi)發(fā)人員、測(cè)試人員以及客戶之間的交互開(kāi)始談起,然后討論如何把測(cè)試當(dāng)作能促進(jìn)協(xié)作的“共同語(yǔ)言”。在討論ATDD促進(jìn)協(xié)作等特征之前,我們先來(lái)理清ATDD和TDD之間的關(guān)系吧。這兩者名字很相似,必定存在某種聯(lián)系。1.4.1名字的含義從ATDD的名字“驗(yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā)”中可以看出,這種技術(shù)與“測(cè)試驅(qū)動(dòng)開(kāi)發(fā)”有一定關(guān)系?!膀?yàn)收測(cè)試驅(qū)動(dòng)開(kāi)發(fā)”中的“測(cè)試驅(qū)動(dòng)開(kāi)發(fā)”部分顯然源自TDD,那么剩下的部分究竟有何含義?驗(yàn)收測(cè)試是什么?簡(jiǎn)單地說(shuō),驗(yàn)收測(cè)試是用來(lái)檢測(cè)某項(xiàng)功能的完成情況的。如果某個(gè)功能的所有驗(yàn)收測(cè)試都通過(guò)了,那么這個(gè)功能也就完成了。無(wú)論用何種格式表述需求,都可以采用ATDD。我們可以把需求記錄在用戶用例、用戶故事或者其他文檔上,都沒(méi)關(guān)系。有時(shí)采用用戶故事管理需求的團(tuán)隊(duì)傾向?qū)⑦@種方法稱為“故事測(cè)試驅(qū)動(dòng)開(kāi)發(fā)”,實(shí)際上還是指同一種技術(shù)。還有人喜歡稱其為“客戶測(cè)試驅(qū)動(dòng)開(kāi)發(fā)”,這樣也可以,因?yàn)轵?yàn)收測(cè)試的所有權(quán)本來(lái)就是客戶的。注意盡管ATDD很像TDD,也借鑒了TDD的許多優(yōu)點(diǎn),但這兩種開(kāi)發(fā)方法可以分開(kāi)使用。那些不使用用戶故事的開(kāi)發(fā)人員,或者實(shí)現(xiàn)功能前不先寫測(cè)試的開(kāi)發(fā)人員,仍然可以使用TDD。那些不用測(cè)試先行的方式寫代碼的團(tuán)隊(duì),仍然可以用ATDD測(cè)試先行地實(shí)現(xiàn)新功能。這些技術(shù)互相彌補(bǔ)互相支撐,結(jié)合使用時(shí)更能發(fā)揮其作用。無(wú)論我們用什么格式或工具來(lái)管理需求,ATDD的主要目的都是促進(jìn)客戶和開(kāi)發(fā)團(tuán)隊(duì)之間的緊密協(xié)作。下面我們來(lái)討論測(cè)試驅(qū)動(dòng)方法為何會(huì)有這種作用。1.4.2緊密協(xié)作在任何由人參與的復(fù)雜活動(dòng)中,緊密協(xié)作都至關(guān)重要,采用ATDD來(lái)開(kāi)發(fā)軟件的活動(dòng)也是如此。我們期望團(tuán)隊(duì)能夠像一個(gè)整體,而不是開(kāi)發(fā)團(tuán)隊(duì)、業(yè)務(wù)分析團(tuán)隊(duì)和測(cè)試團(tuán)隊(duì)截然分開(kāi),更別說(shuō)設(shè)立一個(gè)單獨(dú)的質(zhì)量保證部門了。要想使整個(gè)團(tuán)隊(duì)的生產(chǎn)力達(dá)到最高,團(tuán)隊(duì)成員之間必須能有效溝通,能面對(duì)面地討論如何構(gòu)建高質(zhì)量的軟件。若客戶、測(cè)試人員、業(yè)務(wù)分析人員和開(kāi)發(fā)人員之間用測(cè)試計(jì)劃、需求文檔和測(cè)試報(bào)告來(lái)交流和溝通,那可不行。使用ATDD,我們可以把知識(shí)、技能和能力匯集到一起,有效地協(xié)同工作。下面我們來(lái)看看為什么這種緊密的協(xié)作能提高效率,消除對(duì)需求的誤解,減少返工所帶來(lái)的開(kāi)銷,進(jìn)而幫助我們開(kāi)發(fā)出真正滿足客戶需要的軟件??吹靡?jiàn)摸得著的軟件有些團(tuán)隊(duì)會(huì)悶頭數(shù)月開(kāi)發(fā)軟件,完全不與客戶溝通,這樣開(kāi)發(fā)出來(lái)的軟件很少能讓客戶真正滿意。若能不斷地向客戶展示已完成的功能,當(dāng)軟件中有些功能不正確或者與客戶需求不符時(shí),我們會(huì)立即得到反饋。若能盡早得到反饋,那么我們就能減少項(xiàng)目風(fēng)險(xiǎn),降低成本了。此外,若能把所有完成的功能都展示給客戶看,那么我們會(huì)清楚地了解當(dāng)前進(jìn)度,而不會(huì)像基于文檔開(kāi)發(fā)方式那樣常常樂(lè)觀地誤認(rèn)為任務(wù)“已經(jīng)完成90%了”。建立信任及信心盡早以及頻繁地交付軟件還可以在團(tuán)隊(duì)和客戶之間,以及團(tuán)隊(duì)內(nèi)部建立起信任感。通過(guò)向客戶(以及自己)展示已完成的功能,整個(gè)團(tuán)隊(duì)工作起來(lái)會(huì)更輕松??蛻糇鲋髟隽渴杰浖_(kāi)發(fā)過(guò)程中的客戶權(quán)利,與傳統(tǒng)的瀑布式開(kāi)發(fā)過(guò)程中的客戶權(quán)利有很大差別。在增量式開(kāi)發(fā)過(guò)程中,客戶有權(quán)決定哪些功能要先開(kāi)發(fā)。同樣,若團(tuán)隊(duì)不能在既定的預(yù)算和時(shí)限內(nèi)完工,客戶也可以取消某些功能。功能的開(kāi)發(fā)成本當(dāng)然會(huì)影響到客戶的決策。開(kāi)發(fā)成本由開(kāi)發(fā)人員估算,包括推遲開(kāi)發(fā)的成本,以及改變開(kāi)發(fā)順序后的開(kāi)銷,等等。若用戶可以控制自己的錢花在哪些功能上,那么他們對(duì)待項(xiàng)目的態(tài)度也會(huì)發(fā)生變化。最終由客戶決定他們的錢花在哪些功能上,絕對(duì)可以激發(fā)起客戶的熱情。培養(yǎng)共同語(yǔ)言通過(guò)鼓勵(lì)測(cè)試人員、開(kāi)發(fā)人員以及客戶之間的溝通協(xié)作,我們可以營(yíng)造出一個(gè)氛圍,在這個(gè)氛圍中,有價(jià)值的信息可以在團(tuán)隊(duì)中迅速得以分享。此外,隨著團(tuán)隊(duì)成員間不斷地溝通,相互之間也會(huì)更了解,同時(shí)也會(huì)慢慢培養(yǎng)出共同的語(yǔ)言,這樣溝通效率會(huì)變得更高。軟件開(kāi)發(fā)是人參與的活動(dòng),無(wú)論如何都不能忘記這一點(diǎn)。我們好好想想,把驗(yàn)收測(cè)試作為整個(gè)團(tuán)隊(duì)(包括客戶、測(cè)試人員及開(kāi)發(fā)人員等)溝通時(shí)使用的共同語(yǔ)言的基礎(chǔ),是否可行呢?1.4.3把測(cè)試作為溝通的共同語(yǔ)言需求不清是軟件開(kāi)發(fā)中最大的問(wèn)題之一。要清晰地表達(dá)出需求,保證需求在記述過(guò)程中還能保持原樣,絕不是件容易的事情。有些人甚至認(rèn)為這根本不可能,畢竟我們不會(huì)讀心術(shù)1。1如果你可以,請(qǐng)馬上聯(lián)系我們,公司還有一個(gè)高級(jí)客戶讀心術(shù)專家的職位空缺。當(dāng)用文檔(例如需求文檔)作為溝通的媒介時(shí)問(wèn)題尤為突出。文檔絕對(duì)不是傳遞信息和交流的好方法。若我們能把客戶需求轉(zhuǎn)化成可執(zhí)行測(cè)試,通過(guò)測(cè)試來(lái)驗(yàn)證系統(tǒng)是否滿足客戶的需求,那么問(wèn)題就會(huì)少很多。這就是“以測(cè)試為規(guī)約”的好處。以測(cè)試為規(guī)約若把測(cè)試當(dāng)作需求的嚴(yán)格表述形式,那么至少在理論上能通過(guò)所有測(cè)試的系統(tǒng)一定能夠滿足客戶的需求,當(dāng)然測(cè)試一定要充分覆蓋系統(tǒng)的每個(gè)部分才行。不過(guò)在現(xiàn)實(shí)的軟件開(kāi)發(fā)項(xiàng)目中,要做到這一點(diǎn)并不容易。測(cè)試不會(huì)發(fā)現(xiàn)所有的缺陷,有一定商業(yè)軟件項(xiàng)目經(jīng)驗(yàn)的人絕不會(huì)因此感到困惑。部分原因是我們漏掉了一些應(yīng)該想到的測(cè)試,還有一部分原因是人類的本性和常常欺騙我們的直覺(jué)促使我們跳過(guò)一些測(cè)試以節(jié)約時(shí)間。如果測(cè)試無(wú)論如何都不會(huì)徹底,那么把測(cè)試當(dāng)作規(guī)約還有意義嗎?測(cè)試真的可以描述需求,定義概念嗎?當(dāng)然把測(cè)試當(dāng)作規(guī)約,不會(huì)解決所有的問(wèn)題,但其確實(shí)也有一些顯著的優(yōu)點(diǎn):可以自動(dòng)化執(zhí)行,更快地提供反饋;能更可靠地執(zhí)行測(cè)試;少一個(gè)翻譯的步驟。首先,許多費(fèi)時(shí)費(fèi)力的測(cè)試工作都可以讓自動(dòng)化的可執(zhí)行測(cè)試來(lái)做,比起手工測(cè)試,自動(dòng)化測(cè)試還能顯著縮短反饋周期。其次,計(jì)算機(jī)不會(huì)感到疲倦,也不會(huì)偷懶,讓它幫忙做測(cè)試能夠避免我們?nèi)祟惻c生俱來(lái)的人性弱點(diǎn)。再次,反正把需求記錄在文檔上注定會(huì)有問(wèn)題,現(xiàn)在只不過(guò)把記錄需求換成記錄測(cè)試用例而已,事情還能糟糕到哪去呢?雖然把需求轉(zhuǎn)化成測(cè)試的過(guò)程中仍然可能出現(xiàn)問(wèn)題,但是在執(zhí)行測(cè)試時(shí)知識(shí)轉(zhuǎn)化量越少,需要人解讀的地方越少,出錯(cuò)的機(jī)會(huì)就越少。以例子為規(guī)約此外,以測(cè)試為規(guī)約的最顯著的好處在于可以采用“以例子為規(guī)約”方式來(lái)描述需求,而不是用抽象的描述(當(dāng)然了,在表述需求時(shí),總是要有部分描述性文字的)。換言之,以例子為規(guī)約的需求表述方式提倡用例子表達(dá)需求,例如“若訂閱價(jià)格為$20,稅率為10%,那么系統(tǒng)應(yīng)當(dāng)從用戶賬戶中共收取$22”,而不是用傳統(tǒng)的需求文檔中常見(jiàn)的“系統(tǒng)應(yīng)當(dāng)計(jì)算稅”這種表述形式。對(duì)于簡(jiǎn)單的需求,基于例子的方法并不比傳統(tǒng)的“系統(tǒng)應(yīng)當(dāng)這樣……”的方法好多少,畢竟我們都知道簡(jiǎn)單的計(jì)算稅金的方法。然而并不是所有功能都這么簡(jiǎn)單,對(duì)于復(fù)雜的業(yè)務(wù)邏輯,誤解的可能性會(huì)高得多。例如某項(xiàng)特定的交易需要交多種稅,而且不同的交易地點(diǎn)有不同的稅金計(jì)算規(guī)則,這時(shí)具體的例子會(huì)很有用。以例子為規(guī)約的方式很符合我們的直覺(jué),而且還很容易把需求聯(lián)系到真實(shí)世界以及我們要開(kāi)發(fā)的軟件上。在TDD中也能使用以例子為規(guī)約的方式。只不過(guò)以例子為規(guī)約的驗(yàn)收測(cè)試表述的是系統(tǒng)的功能,而以例子為規(guī)約的單元測(cè)試表述的是代碼實(shí)現(xiàn)而已。軟件內(nèi)部質(zhì)量和外部質(zhì)量都很高,我們對(duì)工作也更有信心,客戶也因?yàn)檐浖耆珴M足了需要而非常樂(lè)于和我們合作——要是能做到這些那就最好了。畢竟,在理論上,一切皆有可能。下面我們就會(huì)停止理論層面的分析,開(kāi)始講解真正的實(shí)踐技巧。不過(guò)在那之前,我們先來(lái)討論有哪些工具可以使用。1.5TDD工具工具十分重要。想象一下若沒(méi)有編譯器、編輯器以及操作系統(tǒng),軟件開(kāi)發(fā)將會(huì)多么困難。經(jīng)過(guò)數(shù)十年的技術(shù)進(jìn)步,軟件開(kāi)發(fā)工作已經(jīng)大大簡(jiǎn)化了。我們?cè)谑褂肨DD開(kāi)發(fā)程序時(shí)也一樣,好的工具能使情況大為改觀。下面我們將會(huì)簡(jiǎn)要介紹三種主要的工具和技術(shù):?jiǎn)卧獪y(cè)試框架、持續(xù)集成及其實(shí)現(xiàn)工具以及代碼覆蓋率。1.5.1使用xUnit做單元測(cè)試KentBeck在很多年以前用SmallTalk語(yǔ)言編寫了一個(gè)叫做SUnit(\h)的單元測(cè)試框架。這個(gè)框架在軟件開(kāi)發(fā)社區(qū)內(nèi)掀起了一陣狂潮,隨后幾乎所有語(yǔ)言都有了自己的移植版本1。對(duì)Java開(kāi)發(fā)者來(lái)說(shuō),JUnit是標(biāo)準(zhǔn)的單元測(cè)試工具,它也是由SUnit移植而來(lái)(可以在\h/下載)。所有這些參照SUnit或JUnit而設(shè)計(jì)的單元測(cè)試框架都相似,因此被統(tǒng)稱為xUnit。只要掌握其中一種,其他的也就不難學(xué)習(xí)了(只要你熟悉對(duì)應(yīng)的編程語(yǔ)言就行)。1SUnit和JUnit在ColdFusion、C#、C、C++、Delphi、ActionScript、Haskell、JavaScript、VisualBasic、JScript、Lingo、LISP、Lua、Objective-C、PL/SQL、Perl、PHP、PowerBuilder、Python、Ruby、Scheme、Tcl/Tk及KornShell等語(yǔ)言中都有移植版本。RonJeffries維護(hù)了一個(gè)xUnit框架的列表:\h/software.htm。xUnit中所定義的單元測(cè)試框架到底是何概念?單元測(cè)試框架是指能夠輔助單元測(cè)試編寫、測(cè)試以及報(bào)告的框架。例如JUnit提供了許多基類供開(kāi)發(fā)人員擴(kuò)展,還提供了一些比較執(zhí)行結(jié)果的類和接口,等等??蚣苓€會(huì)提供不同的測(cè)試執(zhí)行器(testrunner)來(lái)運(yùn)行單元測(cè)試。這個(gè)類會(huì)收集所有測(cè)試類,執(zhí)行之,然后收集測(cè)試結(jié)果,最后用文本或者圖形的形式將測(cè)試結(jié)果展現(xiàn)給開(kāi)發(fā)人員。本書中我們將會(huì)使用JUnit及Java,實(shí)際上還會(huì)用到許多JUnit的擴(kuò)展,以測(cè)試驅(qū)動(dòng)不同的組件。如果你還不熟悉JUnit,參閱附錄中有關(guān)這個(gè)工具的簡(jiǎn)介。不過(guò)現(xiàn)在不要花太多時(shí)間研究JUnit,我們還會(huì)介紹許多工具,例如支持ATDD的工具等。1.5.2支持ATDD的測(cè)試框架大部分的單元測(cè)試框架都基于xUnit的概念,但在驗(yàn)收測(cè)試世界中,框架的類型就相對(duì)較多了。產(chǎn)生這種差異主要是因?yàn)楣δ軐用娴尿?yàn)收測(cè)試概念相對(duì)較新,在沒(méi)有什么可記錄的情況下,記錄—執(zhí)行模式的自動(dòng)測(cè)試工具就不能正常工作。此外,系統(tǒng)用戶界面的共性比較少也是造成驗(yàn)收測(cè)試框架種類繁多的一個(gè)原因。不過(guò)Web應(yīng)用除外,因?yàn)槲覀兛梢酝ㄟ^(guò)標(biāo)準(zhǔn)的HTTP協(xié)議訪問(wèn)幾乎所有的Web應(yīng)用程序。除了技術(shù)因素之外,是否能很好地支持客戶協(xié)作也是考察這類工具的一個(gè)重要因素。有些驗(yàn)收測(cè)試工具是表格形式的(例如Fit及Fitness),這樣開(kāi)發(fā)人員、測(cè)試人員以及業(yè)務(wù)分析師就能很好的與非技術(shù)背景的客戶協(xié)作編寫測(cè)試了。除了支持表格形式測(cè)試的Fit家族,還有能用純文本編寫測(cè)試的框架,Exactor是這類工具的代表。采用更技術(shù)化的方式編寫測(cè)試有時(shí)也可以,例如腳本語(yǔ)言。而有時(shí)候,即使有許多框架可以選擇,自己編寫一個(gè)測(cè)試框架也未嘗不可。我們將在第三部分討論驗(yàn)收測(cè)試時(shí)詳細(xì)介紹這些工具。現(xiàn)在我們先繼續(xù)討論下一個(gè)工具——持續(xù)集成服務(wù)器。1.5.3持續(xù)集成及構(gòu)建若團(tuán)隊(duì)成員會(huì)不斷改動(dòng)項(xiàng)目各個(gè)部分的代碼,那么開(kāi)發(fā)人員集成完成的工作時(shí)也會(huì)遇到更多問(wèn)題,這種問(wèn)題在傳統(tǒng)開(kāi)發(fā)方式中并不常見(jiàn)。在采用TDD開(kāi)發(fā)方式的團(tuán)隊(duì)中,代碼都是集體共有的(CollectiveCodeOwnership)2,每個(gè)人都可以修改系統(tǒng)任何部分的代碼。2如果開(kāi)發(fā)人員需要征得代碼所有者同意后才能做重構(gòu),那么TDD還能用么?我想不太可能。當(dāng)然事情不是非黑即白的,我聽(tīng)說(shuō)已經(jīng)有團(tuán)隊(duì)成功使用了各種混合代碼所有權(quán)制度。因此只要團(tuán)隊(duì)堅(jiān)持重構(gòu),那么就不斷會(huì)有小的變化持續(xù)地流入代碼庫(kù)。在這種情況下,如果等兩天才提交所有的改動(dòng),則難免要合并代碼以解決沖突,這可不是件有趣的事情?;谝陨显?,我們有必要更頻繁地與代碼庫(kù)保持同步。在開(kāi)發(fā)人員本地代碼與代碼庫(kù)之間的同步,即持續(xù)集成的過(guò)程中,我們不僅要保證代碼能夠集成(能編譯),還要運(yùn)行所有測(cè)試,以保證集成后的代碼正常運(yùn)轉(zhuǎn)。不過(guò)在實(shí)際的項(xiàng)目中我們常需要做些取舍,下面將會(huì)討論?;趯?shí)際情況做取舍進(jìn)行持續(xù)集成實(shí)踐的團(tuán)隊(duì)在提交代碼前常常只運(yùn)行與所作修改相關(guān)的部分測(cè)試,讓持續(xù)集成服務(wù)器在后臺(tái)運(yùn)行所有的測(cè)試(包括單元測(cè)試、集成測(cè)試和功能測(cè)試)。在圖1-12中,持續(xù)集成服務(wù)器在監(jiān)測(cè)代碼庫(kù)的變動(dòng)。實(shí)際上,這是很多團(tuán)隊(duì)需要面對(duì)的很現(xiàn)實(shí)的取舍。運(yùn)行所有的測(cè)試會(huì)耗費(fèi)大量時(shí)間(幾分鐘,甚至幾個(gè)小時(shí)),而開(kāi)發(fā)人員所做的改動(dòng)只會(huì)影響到一小部分的測(cè)試,也就是說(shuō)余下的大部分測(cè)試不會(huì)給開(kāi)發(fā)人員帶來(lái)有價(jià)值的信息。所以在提交代碼前只運(yùn)行相關(guān)的一部分測(cè)試(持續(xù)數(shù)秒)既合情又合理。我們所做的修改偶爾會(huì)破壞掉本機(jī)運(yùn)行的測(cè)試沒(méi)有覆蓋到的功能,不過(guò)持續(xù)集成服務(wù)器通常在5~15分鐘后就能夠發(fā)現(xiàn)最新的代碼中存在的問(wèn)題。你現(xiàn)在是不是已經(jīng)在想如何實(shí)現(xiàn)這種持續(xù)集成服務(wù)器了呢?圖1-12描述了典型的場(chǎng)景。開(kāi)發(fā)人員在本地運(yùn)行一部分自動(dòng)化測(cè)試(1),然后把本地代碼提交到代碼版本控制系統(tǒng)中去(2)。新提交的代碼改動(dòng)會(huì)使持續(xù)集成服務(wù)器開(kāi)始一輪新的構(gòu)建(3)。在新代碼構(gòu)建完成后,持續(xù)集成服務(wù)器會(huì)發(fā)送電子郵件把構(gòu)建報(bào)告發(fā)送給開(kāi)發(fā)人員(4)有多種持續(xù)構(gòu)建服務(wù)器可以挑選要使用持續(xù)集成,我們不用自己實(shí)現(xiàn)持續(xù)集成服務(wù)器,有許多開(kāi)源及商業(yè)的實(shí)現(xiàn)可以選擇。其中較流行的有CruiseControl(\h)、AntHill(\h)、Continuum(\h/continuum)以及Bamboo(\h/software/bamboo/),新的實(shí)現(xiàn)也層出不窮。如果有興趣了解持續(xù)集成背后的原理及相關(guān)工具,請(qǐng)參閱MartinFowler的文章《持續(xù)集成》3。JamesShore也寫了不少關(guān)于持續(xù)集成及持續(xù)集成工具的好文章4。3\h/articles/continuousIntegration.html4\h1.5.4代碼覆蓋率許多開(kāi)發(fā)人員都可能會(huì)熟悉Lint(\h)等靜態(tài)代碼分析工具。不少Java開(kāi)發(fā)人員都用過(guò)PMD(\h)來(lái)檢測(cè)源代碼中的問(wèn)題或者分析源代碼的復(fù)雜性。隨著自動(dòng)化測(cè)試,特別是自動(dòng)化單元測(cè)試的不斷興起,衡量代碼覆蓋率(也叫測(cè)試覆蓋率)的工具也不斷涌現(xiàn)。簡(jiǎn)言之,代碼覆蓋率是用來(lái)衡量自動(dòng)化測(cè)試對(duì)產(chǎn)品代碼的語(yǔ)句、分支及表達(dá)式等的覆蓋程度的指標(biāo)5。5若想了解更多的代碼覆蓋率的原理以及工具,請(qǐng)參照我的文章《代碼覆蓋率簡(jiǎn)介》(\h/newsletter/200401/IntroToCodeCoverage.html)。若在構(gòu)建中檢測(cè)代碼覆蓋率,我們就能很清楚地知道測(cè)試是否充分了。在團(tuán)隊(duì)剛開(kāi)始采用TDD或單元測(cè)試時(shí),這種方式尤其有用,因?yàn)樗苤赋瞿牟糠执a測(cè)試的不夠。就像其他源代碼的衡量標(biāo)準(zhǔn)一樣6,代碼覆蓋率也有可能被人誤用。一味追求高測(cè)試覆蓋率并不是明智的行為。有時(shí)因?yàn)锳PI等的緣故,我們要去測(cè)試那些絕不可能出現(xiàn)的情況才能把覆蓋率從99%提高到100%,因此要恰當(dāng)?shù)厥褂么斯ぞ摺?你聽(tīng)說(shuō)過(guò)按照代碼行數(shù)來(lái)發(fā)工資嗎?以前確實(shí)有過(guò),不過(guò)不是什么好方法。合理的代碼覆蓋率每當(dāng)談及代碼覆蓋率時(shí),總有人會(huì)問(wèn)“覆蓋率的標(biāo)準(zhǔn)應(yīng)當(dāng)是多少?”應(yīng)該是100%,90%,還是80%?答案是,看情況。選擇覆蓋率的標(biāo)準(zhǔn)時(shí),應(yīng)該考慮所用的技術(shù)、語(yǔ)言以及開(kāi)發(fā)工具等,還要考慮其他許多因素。Java及J2EE項(xiàng)目通常會(huì)選擇85%作為標(biāo)準(zhǔn)。之所以是85%,并不是因?yàn)槟承┕δ軟](méi)有測(cè)到,而是因?yàn)檎Z(yǔ)言和API的設(shè)計(jì)風(fēng)格使得100%的覆蓋率不太現(xiàn)實(shí)而已。我曾經(jīng)和一位資深咨詢師及作家J.B.Rainsberger討論過(guò)這個(gè)問(wèn)題,他對(duì)此的看法是“把100%的覆蓋率當(dāng)作目標(biāo),不過(guò)你會(huì)最終發(fā)現(xiàn)85%更加合理”。我們并不打算詳細(xì)討論代碼覆蓋率工具,有興趣的讀者可以去研究一下CenquaClover(\h/clover)、Cobertura(\h)或EMMA(\h)等,這些都是好的代碼覆蓋率工具。工具就介紹到這里吧。在后面的章節(jié)中我們會(huì)學(xué)習(xí)各種測(cè)試的方法,那時(shí)會(huì)接觸到更多有用的工具。不過(guò)現(xiàn)在只要了解單元測(cè)試框架如JUnit的基本原理,以及其與TDD的關(guān)系,就已經(jīng)足夠了。下面我們來(lái)做個(gè)小結(jié),然后就該實(shí)戰(zhàn)了!1.6小結(jié)在本章開(kāi)始部分,我們介紹了在給客戶交付支持組織經(jīng)營(yíng)運(yùn)作的軟件過(guò)程中遇到的問(wèn)題和挑戰(zhàn)。傳統(tǒng)的軟件開(kāi)發(fā)方法在這些方面尚有相當(dāng)大的改進(jìn)余地:制造出的軟件缺陷率高,不容易修改,修改成本高,且不能滿足客戶的真正需要。TDD是一種設(shè)計(jì)和開(kāi)發(fā)方法,它能幫我們從項(xiàng)目開(kāi)始就構(gòu)建出可運(yùn)行的軟件,以增量的方式添加新功能,使軟件在整個(gè)開(kāi)發(fā)過(guò)程中都能工作良好。通過(guò)演進(jìn)式設(shè)計(jì),并且應(yīng)用重構(gòu)優(yōu)化每一步的設(shè)計(jì),我們可以防止代碼質(zhì)量隨著時(shí)間推移而下降。TDD能幫我們正確地做事情。用這種紀(jì)律嚴(yán)明的方法小步前進(jìn),我們可以降低缺陷率并交付高質(zhì)量、易維護(hù),并且容易改動(dòng)的軟件。這樣,要寫的代碼更少,修復(fù)缺陷所用的時(shí)間更短,有更充足的時(shí)間做有用的事情,我們會(huì)更加自信,項(xiàng)目進(jìn)展也會(huì)越來(lái)越快。為了開(kāi)發(fā)出正確的產(chǎn)品,我們可以采用ATDD開(kāi)發(fā)方式。ATDD不是一種測(cè)試技術(shù),而是一個(gè)功能強(qiáng)大的開(kāi)發(fā)工具,它能有效地促進(jìn)客戶、測(cè)試人員、開(kāi)發(fā)人員之間的交流協(xié)作。通過(guò)增量式開(kāi)發(fā)軟件,用面向客戶的測(cè)試作為討論和反饋的基礎(chǔ),ATDD能使整個(gè)團(tuán)隊(duì)緊密地協(xié)作。依照客戶指定的優(yōu)先級(jí),在整個(gè)項(xiàng)目過(guò)
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024新能源出租車新能源電池更換與回收利用合同3篇
- 2024年高速路橋預(yù)制構(gòu)件運(yùn)輸與裝配協(xié)議版B版
- 2024新能源技術(shù)研發(fā)合同技術(shù)指標(biāo)
- 《S活動(dòng)實(shí)戰(zhàn)》課件
- 中國(guó)音樂(lè)學(xué)院《教育機(jī)器人與應(yīng)用》2023-2024學(xué)年第一學(xué)期期末試卷
- 2025年度承包魚塘漁業(yè)知識(shí)產(chǎn)權(quán)保護(hù)合同3篇
- 2024年鋁錠銷售協(xié)議標(biāo)準(zhǔn)版
- 雙十一營(yíng)銷策劃匯報(bào)模板
- 2024順德太平洋商業(yè)房地產(chǎn)項(xiàng)目租賃合同解除及補(bǔ)償協(xié)議3篇
- 幼兒園工作總結(jié)點(diǎn)滴記錄成長(zhǎng)
- 設(shè)備到貨簽收單
- 2021傳播心理學(xué)課程教學(xué)大綱
- 農(nóng)學(xué)技能高考【種植類】復(fù)習(xí)題庫(kù)大全-2、《植物生產(chǎn)與環(huán)境》-下(判斷題)
- 艾瑞咨詢2023年中國(guó)脾虛人群白皮書
- 抖音直播電商項(xiàng)目計(jì)劃書抖音電商創(chuàng)業(yè)商業(yè)計(jì)劃書抖音直播帶貨計(jì)劃書抖音電商運(yùn)營(yíng)方案
- 26個(gè)英文字母描紅字帖
- TCPQS XF003-2023 滅火器產(chǎn)品維修、更換及售后服務(wù)
- htr-pm學(xué)習(xí)課件18燃耗測(cè)量系統(tǒng)
- YY/T 1712-2021采用機(jī)器人技術(shù)的輔助手術(shù)設(shè)備和輔助手術(shù)系統(tǒng)
- 冀教版三年級(jí)下冊(cè)數(shù)學(xué)全冊(cè)教案完整版教學(xué)設(shè)計(jì)
- GB/T 16983-2021化學(xué)試劑二氯甲烷
評(píng)論
0/150
提交評(píng)論