引言
??如果要咱們回答在軟件開發(fā)過(guò)程中最怕的是啥价脾?可能很多人都會(huì)回答改老代碼侨把,也可能會(huì)有不少人回答改需求,那有沒有想過(guò)為什么會(huì)害怕改老代碼秋柄,為什么會(huì)害怕改需求骇笔?答案很簡(jiǎn)單嚣崭,怕改壞了雹舀。不敢碰老代碼说榆,怕一改這里寸认,那里就會(huì)莫名其妙的壞了废麻;不喜歡需求變來(lái)變?nèi)ブ蚶ⅲ滦枨笠蛔兞耍暗脑O(shè)計(jì)就不管用了疼燥,很多的代碼結(jié)構(gòu)都可能變化醉者,而代碼一變就怕出問(wèn)題撬即。如果再深思一下,其實(shí)我們怕的核心原因在于沒有一個(gè)快速有效的檢查機(jī)制唱歧,沒有代碼測(cè)試颅崩,讓我馬上就能知道我是不是把哪里改的有問(wèn)題沿后,我的改動(dòng)是不是引入了新的問(wèn)題朽砰,這就是怕的根源。
TDD登場(chǎng)
??對(duì)于上面的問(wèn)題有解決方案嗎饱搏?當(dāng)然有推沸,很簡(jiǎn)單啊券坞,沒有檢查機(jī)制就加上檢查機(jī)制啊恨锚,我們加上完善的代碼測(cè)試就行啦。這就到本文的主題了课舍,TDD(Test Driven Development)筝尾,測(cè)試驅(qū)動(dòng)開發(fā)。就是咱們不僅要寫測(cè)試來(lái)建立檢查機(jī)制办桨,還要最先寫測(cè)試筹淫,讓測(cè)試先行,然后再開發(fā)功能呢撞∷鸾可能有人第一次聽這種開發(fā)方式會(huì)非常疑惑,好像很反邏輯殊霞,先寫測(cè)試怎么能行薛匪,功能都還沒有呢,測(cè)個(gè)啥呢脓鹃!
TDD來(lái)源
??其實(shí)呢逸尖,TDD的思想可以說(shuō)是來(lái)自于極限編程(Extreme programming,簡(jiǎn)稱XP)瘸右,很多人不理解極限編程是啥娇跟,因?yàn)檫@個(gè)中文翻譯是在是有點(diǎn)爛太颤,這里簡(jiǎn)單的介紹下極限編程乞封。我的理解其實(shí)極限編程很簡(jiǎn)單肃晚,它的核心思想一句話來(lái)說(shuō)就是 “如果我們認(rèn)為一個(gè)實(shí)踐是好的关串,那就把它做到極致”。比如說(shuō),我們認(rèn)為寫測(cè)試是好的實(shí)踐落剪,那我們就做到極致屁倔,先寫測(cè)試再開發(fā),這就有了TDD钞翔;我們認(rèn)為集成部署是好的實(shí)踐,那我們就做到極致,持續(xù)集成持續(xù)部署萝毛,這就有了CICD环揽。個(gè)人認(rèn)為這就是TDD開發(fā)方式的思想由來(lái)秧了。
TDD原理
??因?yàn)門DD是一個(gè)比較寬泛的詞匯衡创,不同的人可能有不同的理解,這里特別說(shuō)明一下的是, 文章涉及到 TDD 專指 UTDD(Unit Test Driven Development)椰苟。
??鋪墊了這么多,那下面就來(lái)看看TDD到底是怎么“玩”的洁仗。TDD其實(shí)就三個(gè)步驟她奥,測(cè)試不可運(yùn)行,測(cè)試可運(yùn)行,代碼重構(gòu)讳苦。來(lái)解釋一下:
??1. 測(cè)試不可運(yùn)行 :寫一個(gè)功能最小完備的單元測(cè)試,并且該單元測(cè)試編譯是不能通過(guò)的芭挽。
??2. 測(cè)試可運(yùn)行 :快速編寫剛剛好使測(cè)試通過(guò)的代碼薛闪,不需要多寫,夠用就行苔可。
??3. 代碼重構(gòu) :消除剛剛編碼過(guò)程引入的重復(fù)設(shè)計(jì)硕并,優(yōu)化設(shè)計(jì)結(jié)構(gòu),當(dāng)然重構(gòu)并不專指對(duì)功能代碼,如果測(cè)試代碼有重復(fù)和不合理的地方么库,也是一樣需要重構(gòu)的亏掀。
??然后不停的重復(fù)這三個(gè)步驟怜校。這就是TDD的原理裙顽,很簡(jiǎn)單,對(duì)吧!
TDD實(shí)戰(zhàn)
??下面來(lái)點(diǎn)具體的實(shí)踐,實(shí)戰(zhàn)一下TDD的三個(gè)步驟。
??假設(shè)現(xiàn)在需要實(shí)現(xiàn)的功能是接收一串字符串參數(shù),經(jīng)過(guò)處理后,輸出:“Hello 字符串”;如果輸入?yún)?shù)為空字符串,就輸出 “Hello world”。如果用TDD的方式來(lái)開發(fā)的話指黎,我們先思考一下這個(gè)功能亲怠,可以分為兩種情況习勤,一是有input,二是input為空,所以咱們可能需要兩個(gè)測(cè)試见坑。(這里用java來(lái)實(shí)現(xiàn))
??咱們來(lái)先寫第一個(gè)測(cè)試:
@Test
public void notEmptyInput() {
String input = "mockInput";
String output = new OutputService().getOutput(input);
assertEquals("Hello mockInput", output);
}
??好,現(xiàn)在這個(gè)一定是無(wú)法測(cè)試通過(guò)的,我們甚至都沒有一個(gè)OutputService, 更別談?wù){(diào)用它的getOutput方法了∈趿撸現(xiàn)在需要讓這個(gè)測(cè)試通過(guò)传藏,我們來(lái)寫一個(gè)剛好夠用的實(shí)現(xiàn)做修。
public class OutputService {
public String getOutput(String input) {
return "Hello" + " " + input;
}
}
??現(xiàn)在運(yùn)行這個(gè)測(cè)試宾濒,應(yīng)該是能通過(guò)的卸奉。目前代碼足夠簡(jiǎn)潔,我們不需要考慮重構(gòu)榄棵,我們?cè)趤?lái)考慮寫下一個(gè)測(cè)試疹鳄,目前空字符串的情況我們還沒有考慮到瘪弓,我們來(lái)寫一個(gè)字符串為空的測(cè)試瓢喉。
@Test
public void emptyInput() {
String input = "";
String output = new OutputService().getOutput(input);
assertEquals("Hello world", output);
}
??現(xiàn)在這個(gè)測(cè)試肯定是無(wú)法通過(guò)運(yùn)行的走贪,現(xiàn)在我們需要修改實(shí)現(xiàn)來(lái)讓我們的第二個(gè)測(cè)試通過(guò)凯亮。
public class OutputService {
public String getOutput(String input) {
if("".equals(input)) {
return "Hello world";
} else {
return "Hello" + " " + input;
}
}
}
??好粘拾,現(xiàn)在第二個(gè)測(cè)試應(yīng)該就能通過(guò)了。但是我們看看我們功能實(shí)現(xiàn)的代碼谅阿,if else看著是不是有點(diǎn)扎眼半哟,處理input的方式也沒能抽出來(lái),聞到了代碼的bad smell. 下面我們就來(lái)進(jìn)行一個(gè)簡(jiǎn)單的重構(gòu)签餐。
public class OutputService {
public String getOutput(String input) {
if("".equals(input)) {
input = "world";
}
return handleInput(input);
}
private String handleInput(String input) {
return "Hello " + input;
}
}
??代碼重構(gòu)玩之后寓涨,咱們運(yùn)行一下兩個(gè)測(cè)試,看看改動(dòng)有沒有造成問(wèn)題氯檐。測(cè)試通過(guò)戒良,代表咱們這個(gè)小小的重構(gòu)就完成啦。其實(shí)測(cè)試代碼也可以重構(gòu)的冠摄,測(cè)試中的new OutputService()也是比較重復(fù)的代碼糯崎,咱們可以利用spring框架的自動(dòng)注入來(lái)完成,本文主要講解的是TDD河泳,這里就不多說(shuō)重構(gòu)了沃呢。通過(guò)這個(gè)例子,相信大家能大概理解TDD的工作方式了拆挥。
質(zhì)疑
??那可能有人會(huì)拋出這么一個(gè)疑問(wèn)薄霜,如果我遵循TDD的開發(fā)方式,那我啥時(shí)候做設(shè)計(jì)呢纸兔,我們之前的開發(fā)方法惰瓜,可是先要做完善的設(shè)計(jì),然后再遵循設(shè)計(jì)開發(fā)的啊汉矿。
??其實(shí)我們?cè)趯?shí)戰(zhàn)的例子中可以看到崎坊,我們?cè)趯懙谝粋€(gè)測(cè)試的時(shí)候,就在測(cè)試中寫出了OutputService和getOutput方法洲拇,這其實(shí)就是設(shè)計(jì)的體現(xiàn)奈揍,我們已經(jīng)預(yù)先設(shè)想了實(shí)現(xiàn)的方式了。我們會(huì)在寫測(cè)試的時(shí)候赋续,先設(shè)想系統(tǒng)的設(shè)計(jì)或者功能的設(shè)計(jì)打月,然后在重構(gòu)的時(shí)候,可能會(huì)修改之前的設(shè)計(jì)蚕捉。所以TDD肯定是有設(shè)計(jì)的奏篙。
??個(gè)人覺得這是一個(gè)設(shè)計(jì)的理念的問(wèn)題,瀑布式的開發(fā)方式迫淹,會(huì)在寫代碼之前做完善詳盡的系統(tǒng)和功能設(shè)計(jì)秘通,并且相信當(dāng)前的設(shè)計(jì)就是最好的設(shè)計(jì),代碼開發(fā)只用照著設(shè)計(jì)的方式去寫就好了敛熬。而TDD是將設(shè)計(jì)的過(guò)程分散開來(lái)了肺稀,我們會(huì)在前期做一些設(shè)計(jì),但可能不會(huì)特別詳盡和完整应民,只是一個(gè)大概的版本话原,因?yàn)槲覀兿嘈判枨罂赡茈S時(shí)會(huì)變化夕吻,設(shè)計(jì)也一定會(huì)演進(jìn)的,然后這個(gè)設(shè)計(jì)版本會(huì)在草稿紙上經(jīng)過(guò)幾個(gè)迭代繁仁,讓我們覺得這個(gè)設(shè)計(jì)目前是OK的涉馅,是能滿足功能的,然后我們?cè)儆肨DD去開發(fā)黄虱,在TDD的過(guò)程中再去完善我們的設(shè)計(jì)稚矿,通過(guò)重構(gòu)的方法,通過(guò)我們對(duì)設(shè)計(jì)模式的理解和我們編程的經(jīng)驗(yàn)捻浦,在coding的過(guò)程中慢慢來(lái)找到最好的設(shè)計(jì)晤揣。
總結(jié)
??本文講了TDD的概念,來(lái)源朱灿,原理以及實(shí)例昧识,相信整體來(lái)說(shuō)并不難理解。其實(shí)TDD最難的是堅(jiān)持盗扒,堅(jiān)持測(cè)試先行滞诺,堅(jiān)持重構(gòu),堅(jiān)持改善設(shè)計(jì)环疼,這些都不是復(fù)雜的概念习霹,但知易行難。只有在實(shí)踐中不斷的總結(jié)和思考炫隶,才能真正的掌握這項(xiàng)方法淋叶。本人也在慢慢學(xué)習(xí),文中如果有錯(cuò)誤和不當(dāng)之處伪阶,請(qǐng)大家?guī)兔χ赋鰜?lái)煞檩,最后希望路上有更多的同行者。
參考文獻(xiàn)
《代碼整潔之道》
《測(cè)試驅(qū)動(dòng)開發(fā)的藝術(shù)》