測試驅(qū)動開發(fā)TDD-Test Driven Development是敏捷開發(fā)中的一種實(shí)踐開發(fā)模式,簡單來說即:測試代碼優(yōu)先于產(chǎn)品代碼編寫,通過測試不斷驅(qū)動編寫完善的產(chǎn)品代碼拂到,實(shí)現(xiàn)所需的功能帅刊。
測試驅(qū)動開發(fā)與通常開發(fā)模式在角度和思考方式上都有著本質(zhì)的區(qū)別走芋,并且剛接觸TDD會讓人難以接受全闷。
在通常模式下叉寂,在拿到一個(gè)需求后,經(jīng)過簡單的思考設(shè)計(jì)(通常是紙上畫符)就直接進(jìn)入功能實(shí)現(xiàn)的開發(fā)总珠。在開發(fā)完成后再編寫測試用例并運(yùn)行測試屏鳍,修復(fù)bug讓代碼能工作。而測試驅(qū)動開發(fā)則正好相反局服,在拿到需求后開始寫的第一個(gè)代碼時(shí)測試用例钓瞭。本質(zhì)上是通過測試用例來描述需求,并編寫產(chǎn)品代碼使得測試通過淫奔。
一山涡、 TDD原則
測試驅(qū)動開發(fā)所遵循的三個(gè)基本原則如下:
除非能讓失敗的單元測試通過,否則不允許去編寫任何的產(chǎn)品代碼唆迁。
對于任何功能需求鸭丛,都是先從寫測試用例入手,為滿足測試用例才能去寫產(chǎn)品代碼媒惕。只允許編寫剛好能夠?qū)е率〉膯卧獪y試系吩。 (編譯失敗也屬于一種失敗)
通常在開發(fā)完成后寫的測試用例都是希望能通過的測試用例妒蔚,很可能因先入為主導(dǎo)致不能正確覆蓋測試。相反月弛,TDD編寫新的測試用例是為了覆蓋不同的需求肴盏,導(dǎo)致失敗。只允許編寫剛好能夠使一個(gè)失敗的單元測試通過的產(chǎn)品代碼帽衙。
編寫的生產(chǎn)代碼只能是為了使一個(gè)失敗的單元測試通過菜皂,不應(yīng)編寫多余的實(shí)現(xiàn)代碼。如果過多編寫了實(shí)現(xiàn)其他功能業(yè)務(wù)的代碼厉萝,則違反了TDD的原則恍飘。
測試驅(qū)動開發(fā)的基本流程:
- 編寫單元測試 --> 運(yùn)行單元測試-失敗
- 編寫生產(chǎn)代碼 --> 運(yùn)行單元測試
- 重構(gòu)代碼 -->運(yùn)行單元測試保證通過
從流程還可以看出,測試驅(qū)動開發(fā)將持續(xù)的重構(gòu)納入流程之重谴垫,并通過測試保證了重構(gòu)的正確性章母。因?yàn)閱卧獪y試運(yùn)行失敗為紅色,通過為綠色翩剪,因此TDD流程也可以簡單描述為:
紅乳怎、綠、重構(gòu)前弯。
二蚪缀、 TDD實(shí)踐
下面通過經(jīng)典的Bob大叔關(guān)于保齡球計(jì)分的例子來進(jìn)行說明秫逝,如何來實(shí)踐TDD。
首先簡單介紹下保齡球積分規(guī)則:
- 每一局總共有十輪询枚,每輪一開始會有十支球瓶违帆,球手可以扔兩次球,目標(biāo)就是用盡量少的球把全部球瓶擊倒金蜀。
- 如果第一球就把全部的球瓶都擊倒了前方,也就是STRIKE-全中,就算完成一輪了廉油,本輪分?jǐn)?shù)是10分再加獎(jiǎng)勵(lì)(bonus):即后面兩球的倒瓶數(shù)惠险,
- 如果第一球沒有全倒,就要再打一球抒线,如果第二球?qū)⑹O碌那蚱咳紦舻拱喙簿褪荢PARE-補(bǔ)中,也算完成一輪嘶炭,本輪分?jǐn)?shù)為10分再加獎(jiǎng)勵(lì)(bonus)即:下一球的倒瓶數(shù)抱慌,
- 如果第二球也沒有把球瓶全部擊倒的話,那分?jǐn)?shù)就是第一球加第二球倒的瓶數(shù)眨猎,沒有獎(jiǎng)勵(lì)(bonus)抑进,再接著打下一輪。依此類推睡陪。
- 如果在第十輪出現(xiàn)STRIKE或者SPARE寺渗,則球手可再加打第三球,最多只能打三球。
- 全部十輪的得分相加就等于這一局的總得分。
我們的需求是:
提供一個(gè)Game類荧库,并且包含2個(gè)方法
- roll(int pins):用于玩家每次扔球后擊倒的瓶子數(shù),參數(shù)即為擊倒的瓶子數(shù)
- score():每局結(jié)束后調(diào)用計(jì)算這一局的總分?jǐn)?shù)涡拘。
根據(jù)TDD的原則,拿到需求后我們第一反應(yīng)不是去設(shè)計(jì)并實(shí)現(xiàn)上述規(guī)則据德,而是應(yīng)當(dāng)先編寫測試用例鳄乏。
1. 第一個(gè)測試用例
先編寫一個(gè)最簡單的測試用例,即每次投球都沒有擊倒瓶子棘利,很明顯最終得分應(yīng)該是0橱野。
很明顯因?yàn)镚ame類不存在,測試是不會通過的赡译,因此我們需要去創(chuàng)建Game類使測試通過(在這里不應(yīng)當(dāng)寫更多實(shí)現(xiàn)代碼)仲吏。
接下來,我們增加投球的代碼,假如每次都沒擊中裹唆,即倒瓶數(shù)都是0:因?yàn)閞oll方法不存在誓斥,因此需要我們在Game類中增加roll方法(此時(shí)不用關(guān)心具體的實(shí)現(xiàn))。
同樣的增加一局結(jié)束后算分方法(同樣不關(guān)心實(shí)現(xiàn))许帐,此時(shí)測試不通過劳坑。
需要修改產(chǎn)品代碼使測試通過,此時(shí)我們不考慮具體實(shí)現(xiàn)成畦,簡單返回一個(gè)0使測試通過距芬。
2. 第二個(gè)測試用例
第一個(gè)測試用例僅為每輪都沒擊倒瓶子的情況,下面我們編寫測試用例每次投球都只擊倒1個(gè)瓶子循帐。即沒有全中也沒有補(bǔ)中框仔,最終得分應(yīng)該是20。
編寫測試用例并運(yùn)行拄养,很顯然測試不會通過离斩,我們需要修改產(chǎn)品代碼。很明顯最簡單的實(shí)現(xiàn)是需要我們累加每次擊球后的分?jǐn)?shù)瘪匿。通過一個(gè)成員變量score記錄分值并累加跛梗,測試通過。
同時(shí)我們發(fā)現(xiàn)測試用例中的循環(huán)投球代碼出現(xiàn)了重復(fù)棋弥,我們可以進(jìn)行代碼的提取重構(gòu)核偿。我們將投球代碼提取為獨(dú)立的方法給測試用例調(diào)用。并再次運(yùn)行測試顽染,保證測試通過漾岳。
3. 第三個(gè)測試用例
現(xiàn)在我們來考慮下出現(xiàn)補(bǔ)中SPARE的情況,并編寫對應(yīng)的測試用例家乘。為了簡單蝗羊,我們先只考慮出現(xiàn)一次全中:第一輪投球?yàn)?,5仁锯;第三球?yàn)?,其他全是沒中-0翔悠。這樣一局得分應(yīng)該是:第一輪10分+(獎(jiǎng)勵(lì)-第三球得分+3)+第二輪得分3+其他得分0 =16业崖。運(yùn)行測試用例-失敗。
我們需要修改產(chǎn)品代碼滿足出現(xiàn)補(bǔ)中的情況蓄愁。這時(shí)我們發(fā)現(xiàn)代碼上有些問題:roll方法本來應(yīng)當(dāng)用來記錄每次擊倒的瓶子數(shù)双炕,實(shí)際卻用來計(jì)算分?jǐn)?shù),而score方法本來應(yīng)用來計(jì)算分?jǐn)?shù)撮抓,但卻沒有實(shí)現(xiàn)妇斤,代碼需要重構(gòu)。
這里我們回退一步,回到第二個(gè)測試用例通過的情況站超,并對代碼進(jìn)行重構(gòu)荸恕。
因?yàn)橐涗浢看螕舻沟钠孔訑?shù),所以我們引入一個(gè)整型數(shù)組死相,并通過一個(gè)索引來指示當(dāng)前是第幾球融求。在roll方法中對擊倒數(shù)進(jìn)行記錄。在score方法中根據(jù)擊倒數(shù)進(jìn)行分值計(jì)算算撮。通過測試用例保證我們的重構(gòu)是正確的生宛。
現(xiàn)在我們回到第三個(gè)測試用例,它依然是未通過狀態(tài)肮柜。因?yàn)檠a(bǔ)中為2球擊倒10球陷舅,因此在計(jì)算得分時(shí)需要按照2球一輪來進(jìn)行考慮,同時(shí)來處理補(bǔ)中的情況审洞。修改產(chǎn)品代碼使測試用例通過莱睁。
現(xiàn)在來會看我們的代碼,可以發(fā)現(xiàn)為了解釋代碼我們加了一些注釋(spare)预明,說明代碼不能自解釋缩赛。同時(shí)我們代碼中有隨意定義的變量i和很長的條件語句,我們對代碼先進(jìn)行一些重構(gòu)撰糠,提取方法酥馍,并修改變量名為有意義的名字。測試用例保證了我們的修改不會破壞當(dāng)前代碼阅酪。
進(jìn)入下一個(gè)紅旨袒、綠、重構(gòu)循環(huán)术辐,完成全中STRIKE的支持和代碼優(yōu)化砚尽。
三、 總結(jié)
上面就是一個(gè)TDD的實(shí)踐過程辉词,從中我們可以看出這樣幾點(diǎn):
- 單元測試保證了編碼過程中持續(xù)重構(gòu)的正確必孤。在完成代碼的編寫后,我們會得到很多的測試代碼瑞躺,而這些測試代碼可以用來保證對代碼后續(xù)的維護(hù)修改的正確性敷搪。單元測試對代碼的覆蓋率同樣可以增強(qiáng)我們對代碼的信心。
- 豐富的單元測試完全可以替代接口文檔幢哨,更能清楚的描述代碼實(shí)現(xiàn)的功能赡勘。
- TDD的開發(fā)模式促使了代碼的抽取和解耦,利于代碼復(fù)用捞镰。
TDD在現(xiàn)實(shí)中的情況:
- 在實(shí)際情況中真正應(yīng)用TDD模式來進(jìn)行開發(fā)的公司和團(tuán)隊(duì)少之又少闸与,具體分析大概有以下原因:
- 公司或團(tuán)隊(duì)沒有推動支持毙替。部分公司和團(tuán)隊(duì)甚至不能很好的推行單元測試,TDD更無從談起践樱。另外雖然TDD能保證產(chǎn)出更高質(zhì)量的代碼厂画,但會被認(rèn)為可能需要耗費(fèi)更多時(shí)間。
- TDD模式跟通常的思維習(xí)慣相反映胁,讓開發(fā)人員很難接受木羹。
- 某些項(xiàng)目運(yùn)行單元測試耗時(shí)太長。即使有很多輔助工具的使用解孙,在一個(gè)復(fù)雜項(xiàng)目里運(yùn)行單元測試仍然不是一個(gè)隨手操作的事情坑填。
參考文檔:
Bobo大叔保齡球例子 可以下載講解PPT
簡書上一個(gè)更詳細(xì)的文章