本文結(jié)構(gòu):
什么是 TDDTDD 有廣義和狹義之分,,常說的是狹義的 TDD,,也就是 UTDD(Unit Test Driven Development)。廣義的 TDD 是 ATDD(Acceptance Test Driven Development),,包括 BDD(Behavior Driven Test Development)和 Consumer-Driven Contracts Development 等,。本文所說的 TDD 指狹義上的 TDD,也就是「單元測試驅(qū)動開發(fā)」,。
TDD 有三層含義:
為什么要 TDD傳統(tǒng)編碼方式 VS TDD 編碼方式傳統(tǒng)編碼方式
TDD 編碼方式
TDD 的好處降低開發(fā)者負(fù)擔(dān)通過明確的流程,,讓我們一次只關(guān)注一個點,思維負(fù)擔(dān)更小,。 保護(hù)網(wǎng)TDD 的好處是覆蓋完全的單元測試,,對產(chǎn)品代碼提供了一個保護(hù)網(wǎng),讓我們可以輕松地迎接需求變化或改善代碼的設(shè)計,。所以如果你的項目需求穩(wěn)定,,一次性做完,后續(xù)沒有任何改動的話,,能享受到 TDD 的好處就比較少了,。 提前澄清需求先寫測試可以幫助我們?nèi)ニ伎夹枨螅⑻崆俺吻逍枨蠹?xì)節(jié),,而不是代碼寫到一半才發(fā)現(xiàn)不明確的需求,。 快速反饋有很多人說 TDD 時,,我的代碼量增加了,所以開發(fā)效率降低了,。但是,,如果沒有單元測試,你就要手工測試,,你要花很多時間去準(zhǔn)備數(shù)據(jù),,啟動應(yīng)用,跳轉(zhuǎn)界面等,,反饋是很慢的,。準(zhǔn)確說,快速反饋是單元測試的好處,。 怎么 TDDTDD 的基本流程是:紅,,綠,重構(gòu),。更詳細(xì)的流程是:
你可能會問,我寫一個測試用例,,它明顯會失敗,,還要運行一下嗎?是的,。你可能以為測試只有成功和失敗兩種情況,,然而,失敗有無數(shù)多種,,運行測試才能保證當(dāng)前的失敗是你期望的失敗,。一切都是為了讓程序符合預(yù)期,這樣當(dāng)出現(xiàn)錯誤的時候,,就能很快定位到錯誤(它一定是剛剛修改的代碼引起的,,因為一分鐘前代碼還是符合我的預(yù)期的)。通過這種方式,,節(jié)省了大量的調(diào)試代碼的時間,。 TDD 的三條規(guī)則
如果違反了會怎么樣呢?違反第一條,,先編寫了產(chǎn)品代碼,,那這段代碼是為了實現(xiàn)什么需求呢?怎么確保它真的實現(xiàn)了呢,?違反第二條,,寫了多個失敗的測試,,如果測試長時間不能通過,會增加開發(fā)者的壓力,,另外,,測試可能會被重構(gòu),這時會增加測試的修改成本,。違反第三條,,產(chǎn)品代碼實現(xiàn)了超出當(dāng)前測試的功能,那么這部分代碼就沒有測試的保護(hù),,不知道是否正確,,需要手工測試??赡苓@是不存在的需求,,那就憑空增加了代碼的復(fù)雜性。如果是存在的需求,,那后面的測試寫出來就會直接通過,,破壞了 TDD 的節(jié)奏感。 我認(rèn)為它的本質(zhì)是:分離關(guān)注點,,一次只戴一頂帽子在我們編程的過程中,,有幾個關(guān)注點:需求,實現(xiàn),,設(shè)計,。TDD 給了我們明確的三個步驟,每個步驟關(guān)注一個方面,。紅:寫一個失敗的測試,,它是對一個小需求的描述,只需要關(guān)心輸入輸出,,這個時候根本不用關(guān)心如何實現(xiàn),。綠:專注在用最快的方式實現(xiàn)當(dāng)前這個小需求,不用關(guān)心其他需求,,也不要管代碼的質(zhì)量多么慘不忍睹,。重構(gòu):既不用思考需求,也沒有實現(xiàn)的壓力,,只需要找出代碼中的壞味道,,并用一個手法消除它,,讓代碼變成整潔的代碼,。 注意力控制人的注意力既可以主動控制,也會被被動吸引,。注意力來回切換的話,,就會消耗更多精力,,思考也會不那么完整。使用 TDD 開發(fā),,我們要主動去控制注意力,,寫測試的時候,發(fā)現(xiàn)一個類沒有定義,,IDE 提示編譯錯誤,,這時候你如果去創(chuàng)建這個類,你的注意力就不在需求上了,,已經(jīng)切換到了實現(xiàn)上,,我們應(yīng)該專注地寫完這個測試,思考它是否表達(dá)了需求,,確定無誤后再開始去消除編譯錯誤,。 為什么很多人做 TDD 都做不起來?不會合理拆分任務(wù)TDD 之前要拆分任務(wù),,把一個大需求拆成多個小需求,。也可以拆出多個函數(shù)來。 不會寫測試什么是有效的單元測試,,有很多人寫測試,,連到底在測什么都不清楚,也可能連斷言都沒有,,通過控制臺輸出,,肉眼對比來驗證。好的單元測試應(yīng)該符合幾條原則:
不會寫剛好的實現(xiàn)很多人寫實現(xiàn)的時候無法專注當(dāng)前需求,,一不小心就把其他需求也實現(xiàn)了,就破壞了節(jié)奏感,。實現(xiàn)的時候不會小步快走,。 不會重構(gòu)不懂什么是 Clean Code,看不出 Smell,,沒有及時重構(gòu),,等想要重構(gòu)時已經(jīng)難以下手了。不知道用合適的「手法」消除 Smell,。 基礎(chǔ)設(shè)施對于特定技術(shù)棧,,沒有把單元測試基礎(chǔ)設(shè)施搭建好,導(dǎo)致寫測試時無法專注在測試用例上,。 實例寫一個程序來計算一個文本文件 words.txt 中每個單詞出現(xiàn)的頻率,。為了保持簡單,假設(shè):
舉個例子,假設(shè) words.txt 包含以下內(nèi)容:
你的程序應(yīng)當(dāng)輸出如下,,按頻率倒序排序:
請先不要往下讀,,思考一下你會怎么做。(思考 3 分鐘...) 新手拿到這樣的需求呢,,就會把所有代碼寫到一個 main() 方法里,,偽代碼如下: main() { 思路很清晰,但往往一口氣寫完,,最后運行起來,,輸出卻不符合預(yù)期,然后就開始打斷點調(diào)試,。 這種代碼沒有任何的封裝,。這就是為什么很多人一聽到說有些公司限制一個方法不超過 10 行,就立馬跳出來說,,這不可能,,10 行能干什么啊,我們的業(yè)務(wù)邏輯很復(fù)雜...這樣的代碼存在什么樣的問題呢,?
好嘛,,那我們來 TDD 嘛,你說讀文件,,輸出控制臺的測試代碼要怎么寫,?當(dāng)然,我們可以通過 Mock 和 Stub 來隔離 IO,,但真的有必要嗎,? 有人問過 Kent Beck 這樣一個問題:
Kent Beck 說:公司請我來是為了實現(xiàn)業(yè)務(wù)價值,,而不是寫測試代碼。所以我只在沒有信心的地方寫測試代碼,。 那對我們這個程序而言,,讀文件和打印到控制臺都是調(diào)用系統(tǒng) API,可以很有信心吧,。最沒有信心的是中間那寫要自己寫的業(yè)務(wù)邏輯,。所以我們可以對程序做一些封裝,《代碼整潔之道》里說,,有注釋的地方都可以抽取方法,,用方法名來代替注釋: main() { 這樣是不是就可以單獨為 等等,你可能會說,,不是測試驅(qū)動設(shè)計嗎,?你怎么開始做設(shè)計了?好問題,!
Kent Beck 不做提前設(shè)計,他會選一個最簡單的用例,,直接開寫,,用最簡單的代碼通過測試。逐漸增加測試,,讓代碼變復(fù)雜,,用重構(gòu)來驅(qū)動出設(shè)計。在這個需求里,,最簡單的場景是什么呢,?那就是文件內(nèi)容為空,輸出也為空,。 當(dāng)然,,對于復(fù)雜問題,可能要一邊寫一邊補充新的用例,,但對于這種簡單的題目,,基本可以提前就想清楚用什么用例驅(qū)動去什么產(chǎn)品代碼。大概可以想到如下的用例:
Martin Fowler 的觀點是,,以前我們寫代碼要做 Big Front Up Design,,在開始寫代碼前要設(shè)計好所有細(xì)節(jié)。而我們有了重構(gòu)這個工具后,,做設(shè)計的壓力小了很多,,因為有測試代碼保護(hù),我們可以隨時重構(gòu)實現(xiàn)了。但這并不代表我們不需要做提前設(shè)計了,,提前設(shè)計可以讓我們可以和他人討論,,可以先迭代幾次再開始寫代碼,在紙上迭代總比改代碼要快,。我個人比較認(rèn)同 Martin Fowler 的做法,,先在腦子里(當(dāng)然,我腦子不夠用,,所以用紙畫)做設(shè)計,,迭代幾次之后再開始寫,這樣,,我還是會用最簡單的實現(xiàn)通過測試,,但重構(gòu)時就有了方向,效率更高,。 回到這個程序,,我發(fā)現(xiàn)目前的封裝不在一個抽象層次上,更理想的設(shè)計是: main() { 這時候,,又有兩種選擇,,有人喜歡自頂向下,有人喜歡自底向上,,我個人更傾向于前者,。 現(xiàn)在開始,只要照著 紅-綠-重構(gòu) 的循環(huán)去做就可以,。大部分 TDD 做不好,,就是沒有前面的任務(wù)分解和列 Example 的過程。想看 TDD 過程的話,,可以參考我做的直播(點擊「閱讀原文」查看),。 FAQ為什么一定要先寫測試,后補測試行不行,?
剛寫了一個測試,,還沒寫實現(xiàn),。明知道運行測試一定會報錯,為什么還要去運行,?
小步快走確實好,但真的需要這么小步嗎,?
測試代碼是否會成為維護(hù)的負(fù)擔(dān),?
為什么要快速實現(xiàn),?
為什么測試代碼要很簡單,?
什么時候不適合 TDD?
學(xué)習(xí)路徑
作者:Seaborn Lee,,程序員,培養(yǎng)者,。致力于用自己的行動,,影響他人做出積極的改變!晨型人,,每日練習(xí)寫作,、繪畫、冥想,、即興幽默,。對「時間管理」和「習(xí)慣養(yǎng)成」有些心得。 |
|