TDD的3條規定 軟件測試
這些年來(lái),我喜歡用下面這三條簡(jiǎn)單的規則來(lái)描述測試驅動(dòng)開(kāi)發(fā):
◆ 除非這能讓失敗的單元測試通過(guò),否則不允許去編寫(xiě)任何的產(chǎn)品代碼。
◆ 只允許編寫(xiě)剛好能夠導致失敗的單元測試。 (編譯失敗也屬于一種失敗)
◆ 只允許編寫(xiě)剛好能夠導致一個(gè)失敗的單元測試通過(guò)的產(chǎn)品代碼。
對于任何功能,一定要從編寫(xiě)它的單元測試開(kāi)始;但是到了原則2,你就不能再為那個(gè)單元測試寫(xiě)更多內容。只要一出現該單元測試代碼編譯失敗,或是斷言失敗,你就必須停下來(lái)開(kāi)始編寫(xiě)產(chǎn)品代碼;但是到了原則3,你就只能編寫(xiě)產(chǎn)品代碼,直到讓測試編譯成功或通過(guò)斷言為準。
仔細想想,就會(huì )發(fā)現如果不是去讓一些東西編譯或是執行,你就根本沒(méi)辦法去寫(xiě)代碼。確實(shí),這也正是關(guān)鍵所在。我們所做的任何事情(無(wú)論是寫(xiě)測試,寫(xiě)產(chǎn)品代碼,或是重構),都要保持系統能夠一直運行。跑通測試的時(shí)間間隔應該是以秒或是分鐘級的,即使那只有10分鐘,也都太長(cháng)了。
想了解實(shí)際的操作過(guò)程,可以看看“保齡球游戲中的Kata”(譯注2)一文。
現今有很多程序員,每當他們頭一次聽(tīng)到這種技術(shù)的時(shí)候,會(huì )想:“這種做法太愚蠢了!”“這會(huì )讓我慢下來(lái),這是時(shí)間和精力的浪費,讓我無(wú)法思考,無(wú)法設計,還會(huì )打亂我的思路!比欢,試想,當你走進(jìn)一個(gè)房間,里面都是用這種方式工作的人們,會(huì )發(fā)生什么情況?你隨便選個(gè)時(shí)間,隨意找個(gè)人,一分鐘以前,他們所有的代碼都跑通過(guò)。
讓我再重復一遍:一分鐘以前每個(gè)人的代碼都能跑通!不管你找誰(shuí),也不管你何時(shí)去找,一分鐘以前他們所有的代碼都跑通了!
如果你所有的代碼自始至終都能跑通,那么你會(huì )多長(cháng)時(shí)間用一次調試器?答案是,不會(huì )太經(jīng)常。只要敲幾下^Z就能很容易的恢復這些代碼到跑通時(shí)的狀態(tài),然后再試著(zhù)把前幾分鐘的代碼寫(xiě)一遍即可。而如果你不經(jīng)常調試,那么會(huì )省去多少時(shí)間呢?而現在你花在調試上的時(shí)間又有多少呢?一旦你有調試過(guò),你又要花上多少時(shí)間來(lái)修復這些bug呢?如果你能夠大大減少這些時(shí)間的話(huà),又會(huì )如何呢?
好處還不只這些。如果你采用這種方法,那么每小時(shí)你都會(huì )產(chǎn)生好幾個(gè)測試;每天就有十幾個(gè);每個(gè)月就幾百個(gè);一年下來(lái),你所編寫(xiě)的測試就會(huì )有數千個(gè)。你可以保留著(zhù)這些測試而且在任何你希望的時(shí)候去運行它們!什么時(shí)候運行它們呢?隨時(shí)!只要你做了改動(dòng),就去運行它們!
有些代碼已經(jīng)混亂不堪了,可是我們?yōu)楹尾蝗デ謇硭鼈兡?我們擔心會(huì )破壞它們。但如果我們擁有測試,我們就有理由確信這些代碼不會(huì )被破壞,或是說(shuō)我們可以很快的就找到被破壞的地方。如果我們有了這些測試,我們就對代碼發(fā)生改變無(wú)所忌憚。如果我們看到混亂的代碼,或是一個(gè)不清晰的結構,可以毫無(wú)顧慮地清理它。因為有了測試,代碼重新變得易修改了;因為有了測試,軟件重新變“軟”了。
好處還不只這些。如果你想要知道如何去調用一個(gè)特定的API,就會(huì )有一個(gè)測試能夠告訴你;如果你想要知道如何創(chuàng )建一個(gè)特定的對象,就會(huì )有個(gè)測試能告訴你。你想知道的任何有關(guān)這個(gè)系統的,都有一個(gè)測試能夠去示意。這些測試就像是小型的設計文檔,小型的代碼示例,描述了系統的工作和使用方式。
你是否曾經(jīng)整合過(guò)一個(gè)第三方庫到你的項目中呢?你拿到一本厚厚的精致的幫助文檔,在結尾處,有一沓薄薄的示例附錄。你會(huì )選擇哪個(gè)去閱讀呢?當然是示例了!那就是單元測試啊!它們是這份文檔中最有用的部分;它們是如何使用代碼的鮮活的例子;它們是極其詳細、完全沒(méi)有歧義的設計文檔,相當的正規甚至可以執行,而且不會(huì )與產(chǎn)品代碼相脫離。
好處還不只這些。如果你曾有過(guò)增加單元測試到一個(gè)可以工作的系統中的經(jīng)歷,你會(huì )發(fā)現那一點(diǎn)兒都不好玩。你很可能會(huì )發(fā)現想跑通測試,你一定是要么去改變系統中的部分設計,要么就在測試上作假。這是因為你試圖去測試的系統并不是基于可測試設計的。例如,你想要測試某個(gè)函數f。然而,f調用了另外一個(gè)從數據庫中刪除記錄的函數。在你的測試中,你不希望這條記錄被刪掉,但卻沒(méi)有辦法來(lái)阻止它。這樣的系統就不是基于可測試設計的系統。
當你遵循TDD的三條規則的時(shí)候,你的所有代碼天生就可測試!而且另一個(gè)能形容“可測試”的詞匯就是“可解耦”。為了單獨的測試一個(gè)模塊,你就必須把它解耦。所以TDD強制你去解耦這些模塊。確實(shí),如果你遵循這三條規則的話(huà),你會(huì )發(fā)現你可能比起從前來(lái)能做出更多的解藕。這就強制了你去創(chuàng )造更好的,低耦合的設計。
收獲了所有的這些好處,這些愚蠢的小規則實(shí)際上好像就沒(méi)那么愚蠢了吧。他們實(shí)際上很可能是一些基本而又深刻的原則。確實(shí),在我接觸TDD之前我曾做過(guò)將近30年的程序員,我不認為曾有過(guò)什么人教過(guò)我什么非常奏效但又基本的編程實(shí)踐。畢竟三十年意味著(zhù)很多的經(jīng)驗。但當我開(kāi)始了使用TDD,我就被這項技術(shù)的有效性所震撼,也沉迷其中。我不會(huì )再考慮去敲一大堆代碼然后平白指望它們能運行成功。對于那種先將一組模塊打散,然后希望能夠再將它們整合起來(lái),并且讓它們在能下個(gè)禮拜五前運行起來(lái)的做法,我也不再能忍受。在我寫(xiě)程序的時(shí)候,每一個(gè)決定都由這種讓現在開(kāi)始寫(xiě)的代碼能在一分鐘后再次執行這樣的基本需求所驅使著(zhù)。
文章來(lái)源于領(lǐng)測軟件測試網(wǎng) http://kjueaiud.com/