一、測(cè)試驅(qū)動(dòng)開發(fā)的基本過(guò)程
????? 1) 明確當(dāng)前要完成的功能。可以記錄成一個(gè) TODO 列表。
????? 2) 快速完成針對(duì)此功能的測(cè)試用例編寫。
????? 3) 測(cè)試代碼編譯不通過(guò)。
????? 4) 編寫對(duì)應(yīng)的功能代碼。
????? 5) 測(cè)試通過(guò)。
????? 6) 對(duì)代碼進(jìn)行重構(gòu),并保證測(cè)試通過(guò)。
????? 7) 循環(huán)完成所有功能的開發(fā)。
二、測(cè)試驅(qū)動(dòng)開發(fā)的原則
????? 1)測(cè)試隔離。不同代碼的測(cè)試應(yīng)該相互隔離。對(duì)一塊代碼的測(cè)試只考慮此代碼的測(cè)試,不要考慮其實(shí)現(xiàn)細(xì)節(jié)(比如它使用了其他類的邊界條件)。
????? 2)一頂帽子。開發(fā)人員開發(fā)過(guò)程中要做不同的工作,比如:編寫測(cè)試代碼、開發(fā)功能代碼、對(duì)代碼重構(gòu)等。做不同的事,承擔(dān)不同的角色。開發(fā)人員完成對(duì)應(yīng)的工作時(shí)應(yīng)該保持注意力集中在當(dāng)前工作上,而不要過(guò)多的考慮其他方面的細(xì)節(jié),保證頭上只有一頂帽子。避免考慮無(wú)關(guān)細(xì)節(jié)過(guò)多,無(wú)謂地增加復(fù)雜度。
????? 3)測(cè)試列表。需要測(cè)試的功能點(diǎn)很多。應(yīng)該在任何階段想添加功能需求問(wèn)題時(shí),把相關(guān)功能點(diǎn)加到測(cè)試列表中,然后繼續(xù)手頭工作。然后不斷的完成對(duì)應(yīng)的測(cè)試用例、功能代碼、重構(gòu)。一是避免疏漏,也避免干擾當(dāng)前進(jìn)行的工作。
????? 4)測(cè)試驅(qū)動(dòng)。這個(gè)比較核心。完成某個(gè)功能,某個(gè)類,首先編寫測(cè)試代碼,考慮其如何使用、如何測(cè)試。然后在對(duì)其進(jìn)行設(shè)計(jì)、編碼。
????? 5)先寫斷言。測(cè)試代碼編寫時(shí),應(yīng)該首先編寫對(duì)功能代碼的判斷用的斷言語(yǔ)句,然后編寫相應(yīng)的輔助語(yǔ)句。
????? 6)可測(cè)試性。功能代碼設(shè)計(jì)、開發(fā)時(shí)應(yīng)該具有較強(qiáng)的可測(cè)試性。其實(shí)遵循比較好的設(shè)計(jì)原則的代碼都具備較好的測(cè)試性。比如比較高的內(nèi)聚性,盡量依賴于接口等。
????? 7)及時(shí)重構(gòu)。無(wú)論是功能代碼還是測(cè)試代碼,對(duì)結(jié)構(gòu)不合理,重復(fù)的代碼等情況,在測(cè)試通過(guò)后,及時(shí)進(jìn)行重構(gòu)。
三、測(cè)試驅(qū)動(dòng)開發(fā)的測(cè)試范圍
????? 按大師 Kent Benk 的話,對(duì)那些你認(rèn)為應(yīng)該測(cè)試的代碼進(jìn)行測(cè)試,測(cè)試驅(qū)動(dòng)開發(fā)強(qiáng)調(diào)測(cè)試并不應(yīng)該是負(fù)擔(dān),而應(yīng)該是幫助我們減輕工作量的方法。
四、TDD的優(yōu)點(diǎn)
????? 『充滿吸引力的優(yōu)點(diǎn)』
????? 完工時(shí)完工。表明我可以很清楚的看到自己的這段工作已經(jīng)結(jié)束了,而傳統(tǒng)的方式很難知道什么時(shí)候編碼工作結(jié)束了。
????? 全面正確的認(rèn)識(shí)代碼和利用代碼,而傳統(tǒng)的方式?jīng)]有這個(gè)機(jī)會(huì)。
????? 為利用你成果的人提供Sample,無(wú)論它是要利用你的源代碼,還是直接重用你提供的組件。
????? 開發(fā)小組間降低了交流成本,提高了相互信賴程度。
????? 避免了過(guò)渡設(shè)計(jì)。
????? 系統(tǒng)可以與詳盡的測(cè)試集一起發(fā)布,從而對(duì)程序的將來(lái)版本的修改和擴(kuò)展提供方便。
????? TDD給了我們自信,讓我們今天的問(wèn)題今天解決,明天的問(wèn)題明天解決,今天不能解決明天的問(wèn)題,因?yàn)槊魈斓膯?wèn)題還沒(méi)有出現(xiàn)(沒(méi)有TestCase),除非有TestCase否則我決不寫任何代碼;明天也不必?fù)?dān)心今天的問(wèn)題,只要我亮了綠燈。
????? 『不顯而易見的優(yōu)點(diǎn)』
????? 逃避了設(shè)計(jì)角色。對(duì)于一個(gè)敏捷的開發(fā)小組,每個(gè)人都在做設(shè)計(jì)。
????? 大部分時(shí)間代碼處在高質(zhì)量狀態(tài),100%的時(shí)間里成果是可見的。
????? 由于可以保證編寫測(cè)試和編寫代碼的是相同的程序員,降低了理解代碼所花費(fèi)的成本。
????? 為減少文檔和代碼之間存在的細(xì)微的差別和由這種差別所引入的Bug作出杰出貢獻(xiàn)。
????? 在預(yù)先設(shè)計(jì)和緊急設(shè)計(jì)之間建立一種平衡點(diǎn),為你區(qū)分哪些設(shè)計(jì)該事先做、哪些設(shè)計(jì)該迭代時(shí)做提供了一個(gè)可靠的判斷依據(jù)。
????? 『有爭(zhēng)議的優(yōu)點(diǎn)』
?????? 事實(shí)上提高了開發(fā)效率。每一個(gè)正在使用TDD并相信TDD的人都會(huì)相信這一點(diǎn),但觀望者則不同,不相信TDD的人甚至堅(jiān)決反對(duì)這一點(diǎn),這很正常,世界總是這樣。
????? 發(fā)現(xiàn)比傳統(tǒng)測(cè)試方式更多的Bug。
????? 使IDE的調(diào)試功能失去意義,或者應(yīng)該說(shuō),避免了令人頭痛的調(diào)試和節(jié)約了調(diào)試的時(shí)間。
????? 總是處在要么編程要么重構(gòu)的狀態(tài)下,不會(huì)使人抓狂。(兩頂帽子)
????? 單元測(cè)試非常有趣。
五、TDD的 優(yōu)勢(shì)
????? TDD的基本思路就是通過(guò)測(cè)試來(lái)推動(dòng)整個(gè)開發(fā)的進(jìn)行。而測(cè)試驅(qū)動(dòng)開發(fā)技術(shù)并不只是單純的測(cè)試工作。
????? 需求向來(lái)就是軟件開發(fā)過(guò)程中感覺最不好明確描述、易變的東西。這里說(shuō)的需求不只是指用戶的需求,還包括對(duì)代碼的使用需求。很多開發(fā)人員最害怕的就是后期還要修改某個(gè)類或者函數(shù)的接口進(jìn)行修改或者擴(kuò)展,為什么會(huì)發(fā)生這樣的事情就是因?yàn)檫@部分代碼的使用需求沒(méi)有很好的描述。測(cè)試驅(qū)動(dòng)開發(fā)就是通過(guò)編寫測(cè)試用例,先考慮代碼的使用需求(包括功能、過(guò)程、接口等),而且這個(gè)描述是無(wú)二義的,可執(zhí)行驗(yàn)證的。
????? 通過(guò)編寫這部分代碼的測(cè)試用例,對(duì)其功能的分解、使用過(guò)程、接口都進(jìn)行了設(shè)計(jì)。而且這種從使用角度對(duì)代碼的設(shè)計(jì)通常更符合后期開發(fā)的需求。可測(cè)試的要求,對(duì)代碼的內(nèi)聚性的提高和復(fù)用都非常有益。因此測(cè)試驅(qū)動(dòng)開發(fā)也是一種代碼設(shè)計(jì)的過(guò)程。
????? 開發(fā)人員通常對(duì)編寫文檔非常厭煩,但要使用、理解別人的代碼時(shí)通常又希望能有文檔進(jìn)行指導(dǎo)。而測(cè)試驅(qū)動(dòng)開發(fā)過(guò)程中產(chǎn)生的測(cè)試用例代碼就是對(duì)代碼的最好的解釋。
????? 快樂(lè)工作的基礎(chǔ)就是對(duì)自己有信心,對(duì)自己的工作成果有信心。當(dāng)前很多開發(fā)人員卻經(jīng)常在擔(dān)心:“代碼是否正確?”“辛苦編寫的代碼還有沒(méi)有嚴(yán)重bug?”“修改的新代碼對(duì)其他部分有沒(méi)有影響?”。這種擔(dān)心甚至導(dǎo)致某些代碼應(yīng)該修改卻不敢修改的地步。測(cè)試驅(qū)動(dòng)開發(fā)提供的測(cè)試集就可以作為你信心的來(lái)源。
????? 當(dāng)然測(cè)試驅(qū)動(dòng)開發(fā)最重要的功能還在于保障代碼的正確性,能夠迅速發(fā)現(xiàn)、定位bug。而迅速發(fā)現(xiàn)、定位bug是很多開發(fā)人員的夢(mèng)想。針對(duì)關(guān)鍵代碼的測(cè)試集,以及不斷完善的測(cè)試用例,為迅速發(fā)現(xiàn)、定位bug提供了條件。
????? 我的一段功能非常復(fù)雜的代碼使用TDD開發(fā)完成,真實(shí)環(huán)境應(yīng)用中只發(fā)現(xiàn)幾個(gè)bug,而且很快被定位解決。您在應(yīng)用后,也一定會(huì)為那種自信的開發(fā)過(guò)程,功能不斷增加、完善的感覺,迅速發(fā)現(xiàn)、定位bug的能力所感染,喜歡這個(gè)技術(shù)的。
????? 那么是什么樣的原理、方法提供上面說(shuō)的這些好處哪?下面我們就看看TDD的原理。
六、TDD的原理
????? 測(cè)試驅(qū)動(dòng)開發(fā)的基本思想就是在開發(fā)功能代碼之前,先編寫測(cè)試代碼。也就是說(shuō)在明確要開發(fā)某個(gè)功能后,首先思考如何對(duì)這個(gè)功能進(jìn)行測(cè)試,并完成測(cè)試代碼的編寫,然后編寫相關(guān)的代碼滿足這些測(cè)試用例。然后循環(huán)進(jìn)行添加其他功能,直到完全部功能的開發(fā)。
????? 我們這里把這個(gè)技術(shù)的應(yīng)用領(lǐng)域從代碼編寫擴(kuò)展到整個(gè)開發(fā)過(guò)程。應(yīng)該對(duì)整個(gè)開發(fā)過(guò)程的各個(gè)階段進(jìn)行測(cè)試驅(qū)動(dòng),首先思考如何對(duì)這個(gè)階段進(jìn)行測(cè)試、驗(yàn)證、考核,并編寫相關(guān)的測(cè)試文檔,然后開始下一步工作,最后再驗(yàn)證相關(guān)的工作。下圖是一個(gè)比較流行的測(cè)試模型:V測(cè)試模型。
【圖 V測(cè)試模型】
????? 在開發(fā)的各個(gè)階段,包括需求分析、概要設(shè)計(jì)、詳細(xì)設(shè)計(jì)、編碼過(guò)程中都應(yīng)該考慮相對(duì)應(yīng)的測(cè)試工作,完成相關(guān)的測(cè)試用例的設(shè)計(jì)、測(cè)試方案、測(cè)試計(jì)劃的編寫。這里提到的開發(fā)階段只是舉例,根據(jù)實(shí)際的開發(fā)活動(dòng)進(jìn)行調(diào)整。相關(guān)的測(cè)試文檔也不一定是非常詳細(xì)復(fù)雜的文檔,或者什么形式,但應(yīng)該養(yǎng)成測(cè)試驅(qū)動(dòng)的習(xí)慣。
????? 關(guān)于測(cè)試模型,還有X測(cè)試模型。這個(gè)測(cè)試模型,我認(rèn)為,是對(duì)詳細(xì)階段和編碼階段進(jìn)行建模,應(yīng)該說(shuō)更詳細(xì)的描述了詳細(xì)設(shè)計(jì)和編碼階段的開發(fā)行為。及針對(duì)某個(gè)功能進(jìn)行對(duì)應(yīng)的測(cè)試驅(qū)動(dòng)開發(fā)。
【圖 X測(cè)試模型】
???? 基本原理應(yīng)該說(shuō)非常簡(jiǎn)單,那么如何進(jìn)行實(shí)際操作哪,下面對(duì)開發(fā)過(guò)程進(jìn)行詳細(xì)的介紹。
七、測(cè)試技術(shù)
????? 1. 測(cè)試范圍、粒度
????? 對(duì)哪些功能進(jìn)行測(cè)試?會(huì)不會(huì)太繁瑣?什么時(shí)候可以停止測(cè)試?這些問(wèn)題比較常見。按大師 Kent Benk 的話,對(duì)那些你認(rèn)為應(yīng)該測(cè)試的代碼進(jìn)行測(cè)試。就是說(shuō),要相信自己的感覺,自己的經(jīng)驗(yàn)。那些重要的功能、核心的代碼就應(yīng)該重點(diǎn)測(cè)試。感到疲勞就應(yīng)該停下來(lái)休息一下。感覺沒(méi)有必要更詳細(xì)的測(cè)試,就停止本輪測(cè)試。
????? 測(cè)試驅(qū)動(dòng)開發(fā)強(qiáng)調(diào)測(cè)試并不應(yīng)該是負(fù)擔(dān),而應(yīng)該是幫助我們減輕工作量的方法。而對(duì)于何時(shí)停止編寫測(cè)試用例,也是應(yīng)該根據(jù)你的經(jīng)驗(yàn),功能復(fù)雜、核心功能的代碼就應(yīng)該編寫更全面、細(xì)致的測(cè)試用例,否則測(cè)試流程即可。
????? 測(cè)試范圍沒(méi)有靜態(tài)的標(biāo)準(zhǔn),同時(shí)也應(yīng)該可以隨著時(shí)間改變。對(duì)于開始沒(méi)有編寫足夠的測(cè)試的功能代碼,隨著bug的出現(xiàn),根據(jù)bug補(bǔ)齊相關(guān)的測(cè)試用例即可。
????? 小步前進(jìn)的原則,要求我們對(duì)大的功能塊測(cè)試時(shí),應(yīng)該先分拆成更小的功能塊進(jìn)行測(cè)試,比如一個(gè)類A使用了類B、C,就應(yīng)該編寫到A使用B、C功能的測(cè)試代碼前,完成對(duì)B、C的測(cè)試和開發(fā)。那么是不是每個(gè)小類或者小函數(shù)都應(yīng)該測(cè)試哪?我認(rèn)為沒(méi)有必要。你應(yīng)該運(yùn)用你的經(jīng)驗(yàn),對(duì)那些可能出問(wèn)題的地方重點(diǎn)測(cè)試,感覺不可能出問(wèn)題的地方就等它真正出問(wèn)題的時(shí)候再補(bǔ)測(cè)試吧。
????? 2. 怎么編寫測(cè)試用例
????? 測(cè)試用例的編寫就用上了傳統(tǒng)的測(cè)試技術(shù)。
????? 操作過(guò)程盡量模擬正常使用的過(guò)程。
????? 全面的測(cè)試用例應(yīng)該盡量做到分支覆蓋,核心代碼盡量做到路徑覆蓋。
????? 測(cè)試數(shù)據(jù)盡量包括:真實(shí)數(shù)據(jù)、邊界數(shù)據(jù)。
????? 測(cè)試語(yǔ)句和測(cè)試數(shù)據(jù)應(yīng)該盡量簡(jiǎn)單,容易理解。
????? 為了避免對(duì)其他代碼過(guò)多的依賴,可以實(shí)現(xiàn)簡(jiǎn)單的樁函數(shù)或樁類(Mock Object)。
????? 如果內(nèi)部狀態(tài)非常復(fù)雜或者應(yīng)該判斷流程而不是狀態(tài),可以通過(guò)記錄日志字符串的方式進(jìn)行驗(yàn)證。
八、Tips
????? 很多朋友有疑問(wèn),“測(cè)試代碼的正確性如何保障?是寫測(cè)試代碼還是寫測(cè)試文檔?”這樣是不是會(huì)陷入“雞生蛋,蛋生雞”的循環(huán)。其實(shí)是不會(huì)的。通常測(cè)試代碼通常是非常簡(jiǎn)單的,通常圍繞著某個(gè)情況的正確性判斷的幾個(gè)語(yǔ)句,如果太復(fù)雜,就應(yīng)該繼續(xù)分解啦。而傳統(tǒng)的開發(fā)過(guò)程通常強(qiáng)調(diào)測(cè)試文檔。但隨著開發(fā)節(jié)奏的加快,用戶需求的不斷變化,維護(hù)高層(需求、概要設(shè)計(jì))的測(cè)試文檔可以,更低層的測(cè)試文檔的成本的確太大了。而且可實(shí)時(shí)驗(yàn)證功能正確性的測(cè)試代碼就是對(duì)代碼最好的文檔。
????? 軟件開發(fā)過(guò)程中,除了遵守上面提到的測(cè)試驅(qū)動(dòng)開發(fā)的幾個(gè)原則外,一個(gè)需要注意的問(wèn)題就是,謹(jǐn)防過(guò)度設(shè)計(jì)。編寫功能代碼時(shí)應(yīng)該關(guān)注于完成當(dāng)前功能點(diǎn),通過(guò)測(cè)試,使用最簡(jiǎn)單、直接的方式來(lái)編碼。過(guò)多的考慮后期的擴(kuò)展,其他功能的添加,無(wú)疑增加了過(guò)多的復(fù)雜性,容易產(chǎn)生問(wèn)題。應(yīng)該等到要添加這些特性時(shí)在進(jìn)行詳細(xì)的測(cè)試驅(qū)動(dòng)開發(fā)。到時(shí)候,有整套測(cè)試用例做基礎(chǔ),通過(guò)不斷重構(gòu)很容易添加相關(guān)特性。
九、FAQ
[什么時(shí)候重構(gòu)?]
如果您在軟件公司工作,就意味著您成天都會(huì)和想通過(guò)重構(gòu)改善代碼質(zhì)量的想法打交道,不僅您如此,您的大部分同事也都如此。可是,究竟什么時(shí)候該重構(gòu),什么情況下應(yīng)該重構(gòu)呢?我相信您和您的同事可能有很多不同的看法,最常見的答案是“該重構(gòu)時(shí)重構(gòu)”,“寫不下去的時(shí)候重構(gòu)”,和“下一次迭代開始之前重構(gòu)”,或者干脆就是“最近沒(méi)時(shí)間,就不重構(gòu)了,下次有時(shí)間的時(shí)候重構(gòu)吧”。正如您已經(jīng)預(yù)見到我想說(shuō)的——這些想法都是對(duì)重構(gòu)的誤解。重構(gòu)不是一種構(gòu)建軟件的工具,不是一種設(shè)計(jì)軟件的模式,也不是一個(gè)軟件開發(fā)過(guò)程中的環(huán)節(jié),正確理解重構(gòu)的人應(yīng)該把重構(gòu)看成一種書寫代碼的方式,或習(xí)慣,重構(gòu)時(shí)時(shí)刻刻有可能發(fā)生。在TDD中,除去編寫測(cè)試用例和實(shí)現(xiàn)測(cè)試用例之外的所有工作都是重構(gòu),所以,沒(méi)有重構(gòu)任何設(shè)計(jì)都不能實(shí)現(xiàn)。至于什么時(shí)候重構(gòu)嘛,還要分開看,有三句話是我的經(jīng)驗(yàn):實(shí)現(xiàn)測(cè)試用例時(shí)重構(gòu)代碼,完成某個(gè)特性時(shí)重構(gòu)設(shè)計(jì),產(chǎn)品的重構(gòu)完成后還要記得重構(gòu)一下測(cè)試用例哦。
[什么時(shí)候設(shè)計(jì)?]
這個(gè)問(wèn)題比前面一個(gè)要難回答的多,實(shí)話實(shí)說(shuō),本人在依照TDD開發(fā)軟件的時(shí)候也常常被這個(gè)問(wèn)題困擾,總是覺得有些問(wèn)題應(yīng)該在寫測(cè)試用例之前定下來(lái),而有些問(wèn)題應(yīng)該在新增一個(gè)一個(gè)測(cè)試用例的過(guò)程中自然出現(xiàn),水到渠成。所以,我的建議是,設(shè)計(jì)的時(shí)機(jī)應(yīng)該由開發(fā)者自己把握,不要受到TDD方式的限制,但是,不需要事先確定的事一定不能事先確定,免得捆住了自己的手腳。
[什么時(shí)候增加新的TestCase?]
沒(méi)事做的時(shí)候。通常我們認(rèn)為,如果你要增加一個(gè)新的功能,那么先寫一個(gè)不能通過(guò)的TestCase;如果你發(fā)現(xiàn)了一個(gè)bug,那么先寫一個(gè)不能通過(guò)的TestCase;如果你現(xiàn)在什么都沒(méi)有,從0開始,請(qǐng)先寫一個(gè)不能通過(guò)的TestCase。所有的工作都是從一個(gè)TestCase開始。此外,還要注意的是,一些大師要求我們每次只允許有一個(gè)TestCase亮紅燈,在這個(gè)TestCase沒(méi)有Green之前不可以寫別的TestCase,這種要求可以適當(dāng)考慮,但即使有多個(gè)TestCase亮紅燈也不要緊,并未違反TDD的主要精神。
[TestCase該怎么寫?]
測(cè)試用例的編寫實(shí)際上就是兩個(gè)過(guò)程:使用尚不存在的代碼和定義這些代碼的執(zhí)行結(jié)果。所以一個(gè)TestCase也就應(yīng)該包括兩個(gè)部分——場(chǎng)景和斷言。第一次寫TestCase的人會(huì)有很大的不適應(yīng)的感覺,因?yàn)槟阒八鶎懙乃袞|西都是在解決問(wèn)題,現(xiàn)在要你提出問(wèn)題確實(shí)不大習(xí)慣,不過(guò)不用擔(dān)心,你正在做正確的事情,而這個(gè)世界上最難的事情也不在于如何解決問(wèn)題,而在于ask the right question!
[TDD能幫助我消除Bug嗎?]
答:不能!千萬(wàn)不要把“測(cè)試”和“除蟲”混為一談!“除蟲”是指程序員通過(guò)自己的努力來(lái)減少bug的數(shù)量(消除bug這樣的字眼我們還是不要講為好^_^),而“測(cè)試”是指程序員書寫產(chǎn)品以外的一段代碼來(lái)確保產(chǎn)品能有效工作。雖然TDD所編寫的測(cè)試用例在一定程度上為尋找bug提供了依據(jù),但事實(shí)上,按照TDD的方式進(jìn)行的軟件開發(fā)是不可能通過(guò)TDD再找到bug的(想想我們前面說(shuō)的“完工時(shí)完工”),你想啊,當(dāng)我們的代碼完成的時(shí)候,所有的測(cè)試用例都亮了綠燈,這時(shí)隱藏在代碼中的bug一個(gè)都不會(huì)露出馬腳來(lái)。
但是,如果要問(wèn)“測(cè)試”和“除蟲”之間有什么聯(lián)系,我相信還是有很多話可以講的,比如TDD事實(shí)上減少了bug的數(shù)量,把查找bug戰(zhàn)役的關(guān)注點(diǎn)從全線戰(zhàn)場(chǎng)提升到代碼戰(zhàn)場(chǎng)以上。還有,bug的最可怕之處不在于隱藏之深,而在于滿天遍野。如果你發(fā)現(xiàn)了一個(gè)用戶很不容易才能發(fā)現(xiàn)的bug,那么不一定對(duì)工作做出了什么杰出貢獻(xiàn),但是如果你發(fā)現(xiàn)一段代碼中,bug的密度或離散程度過(guò)高,那么恭喜你,你應(yīng)該拋棄并重寫這段代碼了。TDD避免了這種情況,所以將尋找bug的工作降低到了一個(gè)新的低度。
[我該為一個(gè)Feature編寫TestCase還是為一個(gè)類編寫TestCase?]
初學(xué)者常問(wèn)的問(wèn)題。雖然我們從TDD的說(shuō)明書上看到應(yīng)該為一個(gè)特性編寫相應(yīng)的TestCase,但為什么著名的TDD大師所寫的TestCase都是和類/方法一一對(duì)應(yīng)的呢?為了解釋這個(gè)問(wèn)題,我和我的同事們都做了很多試驗(yàn),最后我們得到了一個(gè)結(jié)論,雖然我不知道是否正確,但是如果您沒(méi)有答案,可以姑且相信我們。
我們的研究結(jié)果表明,通常在一個(gè)特性的開發(fā)開始時(shí),我們針對(duì)特性編寫測(cè)試用例,如果您發(fā)現(xiàn)這個(gè)特性無(wú)法用TestCase表達(dá),那么請(qǐng)將這個(gè)特性細(xì)分,直至您可以為手上的特性寫出TestCase為止。從這里開始是最安全的,它不會(huì)導(dǎo)致任何設(shè)計(jì)上重大的失誤。但是,隨著您不斷的重構(gòu)代碼,不斷的重構(gòu)TestCase,不斷的依據(jù)TDD的思想做下去,最后當(dāng)產(chǎn)品伴隨測(cè)試用例集一起發(fā)布的時(shí)候,您就會(huì)不經(jīng)意的發(fā)現(xiàn)經(jīng)過(guò)重構(gòu)以后的測(cè)試用例很可能是和產(chǎn)品中的類/方法一一對(duì)應(yīng)的。
[什么時(shí)候應(yīng)該將全部測(cè)試都運(yùn)行一遍?]
Good Question!大師們要求我們每次重構(gòu)之后都要完整的運(yùn)行一遍測(cè)試用例。這個(gè)要求可以理解,因?yàn)橹貥?gòu)很可能會(huì)改變整個(gè)代碼的結(jié)構(gòu)或設(shè)計(jì),從而導(dǎo)致不可預(yù)見的后果,但是如果我正在開發(fā)的是一個(gè)ERP怎么辦?運(yùn)行一遍完整的測(cè)試用例可能將花費(fèi)數(shù)個(gè)小時(shí),況且現(xiàn)在很多重構(gòu)都是由工具做到的,這個(gè)要求的可行性和前提條件都有所動(dòng)搖。所以我認(rèn)為原則上你可以挑幾個(gè)你覺得可能受到本次重構(gòu)影響的TestCase去run,但是如果運(yùn)行整個(gè)測(cè)試包只要花費(fèi)數(shù)秒的時(shí)間,那么不介意你按大師的要求去做。
[什么時(shí)候改進(jìn)一個(gè)TestCase?]
增加的測(cè)試用例或重構(gòu)以后的代碼導(dǎo)致了原來(lái)的TestCase的失去了效果,變得無(wú)意義,甚至可能導(dǎo)致錯(cuò)誤的結(jié)果,這時(shí)是改進(jìn)TestCase的最好時(shí)機(jī)。但是有時(shí)你會(huì)發(fā)現(xiàn),這樣做僅僅導(dǎo)致了原來(lái)的TestCase在設(shè)計(jì)上是臃腫的,或者是冗余的,這都不要緊,只要它沒(méi)有失效,你仍然不用去改進(jìn)它。記住,TestCase不是你的產(chǎn)品,它不要好看,也不要怎么太科學(xué),甚至沒(méi)有性能要求,它只要能完成它的使命就可以了——這也證明了我們后面所說(shuō)的“用Ctrl-C/Ctrl-V編寫測(cè)試用例”的可行性。
但是,美國(guó)人的想法其實(shí)跟我們還是不太一樣,拿托尼巴贊的MindMap來(lái)說(shuō)吧,其實(shí)畫MindMap只是為了表現(xiàn)自己的思路,或記憶某些重要的事情,但托尼卻建議大家把MindMap畫成一件藝術(shù)品,甚至還有很多藝術(shù)家把自己畫的抽象派MindMap拿出來(lái)幫助托尼做宣傳。同樣,大師們也要求我們把TestCase寫的跟代碼一樣質(zhì)量精良,可我想說(shuō)的是,現(xiàn)在國(guó)內(nèi)有幾個(gè)公司能把產(chǎn)品的代碼寫的精良??還是一步一步慢慢來(lái)吧。
[為什么原來(lái)通過(guò)的測(cè)試用例現(xiàn)在不能通過(guò)了?]
這是一個(gè)警報(bào),Red Alert!它可能表達(dá)了兩層意思——都不是什么好意思——1)你剛剛進(jìn)行的重構(gòu)可能失敗了,或存在一些錯(cuò)誤未被發(fā)現(xiàn),至少重構(gòu)的結(jié)果和原來(lái)的代碼不等價(jià)了。2)你剛剛增加的TestCase所表達(dá)的意思跟前面已經(jīng)有的TestCase相沖突,也就是說(shuō),新增的功能違背了已有的設(shè)計(jì),這種情況大部分可能是之前的設(shè)計(jì)錯(cuò)了。但無(wú)論哪錯(cuò)了,無(wú)論是那層意思,想找到這個(gè)問(wèn)題的根源都比TDD的正常工作要難。
[我怎么知道那里該有一個(gè)方法還是該有一個(gè)類?]
這個(gè)問(wèn)題也是常常出現(xiàn)在我的腦海中,無(wú)論你是第一次接觸TDD或者已經(jīng)成為TDD專家,這個(gè)問(wèn)題都會(huì)纏繞著你不放。不過(guò)問(wèn)題的答案可以參考前面的“什么時(shí)候設(shè)計(jì)”一節(jié),答案不是唯一的。其實(shí)多數(shù)時(shí)候你不必考慮未來(lái),今天只做今天的事,只要有重構(gòu)工具,從方法到類和從類到方法都很容易。
[我要寫一個(gè)TestCase,可是不知道從哪里開始?]
從最重要的事開始,what matters most?從腳下開始,從手頭上的工作開始,從眼前的事開始。從一個(gè)沒(méi)有UI的核心特性開始,從算法開始,或者從最有可能耽誤時(shí)間的模塊開始,從一個(gè)最嚴(yán)重的bug開始。這是TDD主義者和鼠目寸光者的一個(gè)共同點(diǎn),不同點(diǎn)是前者早已成竹在胸。
[為什么我的測(cè)試總是看起來(lái)有點(diǎn)愚蠢?]
哦?是嗎?來(lái),握個(gè)手,我的也是!不必?fù)?dān)心這一點(diǎn),事實(shí)上,大師們給的例子也相當(dāng)愚蠢,比如一個(gè)極端的例子是要寫一個(gè)兩個(gè)int變量相加的方法,大師先斷言2+3=5,再斷言5+5=10,難道這些代碼不是很愚蠢嗎?其實(shí)這只是一個(gè)極端的例子,當(dāng)你初次接觸TDD時(shí),寫這樣的代碼沒(méi)什么不好,以后當(dāng)你熟練時(shí)就會(huì)發(fā)現(xiàn)這樣寫沒(méi)必要了,要記住,謙虛是通往TDD的必經(jīng)之路!從經(jīng)典開發(fā)方法轉(zhuǎn)向TDD就像從面向過(guò)程轉(zhuǎn)向面向?qū)ο?/a>一樣困難,你可能什么都懂,但你寫出來(lái)的類沒(méi)有一個(gè)純OO的!我的同事還告訴我真正的太極拳,其速度是很快的,不比任何一個(gè)快拳要慢,但是初學(xué)者(通常是指學(xué)習(xí)太極拳的前10年)太不容易把每個(gè)姿勢(shì)都做對(duì),所以只能慢慢來(lái)。
[什么場(chǎng)合不適用TDD?]
問(wèn)的好,確實(shí)有很多場(chǎng)合不適合使用TDD。比如對(duì)軟件質(zhì)量要求極高的軍事或科研產(chǎn)品——神州六號(hào),人命關(guān)天的軟件——醫(yī)療設(shè)備,等等,再比如設(shè)計(jì)很重要必須提前做好的軟件,這些都不適合TDD,但是不適合TDD不代表不能寫TestCase,只是作用不同,地位不同罷了。