域名預(yù)訂/競(jìng)價(jià),好“米”不錯(cuò)過(guò)
提起測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(以下簡(jiǎn)稱"TDD"),圈內(nèi)工程師對(duì)其都有一定程度的了解,TDD 的優(yōu)點(diǎn)也得到了普遍的認(rèn)可。有研究機(jī)構(gòu)曾對(duì)微軟和 IBM 的八個(gè)開(kāi)發(fā)小組進(jìn)行了對(duì)照測(cè)試,結(jié)果發(fā)現(xiàn)使用了 TDD 的小組比未使用的小組在問(wèn)題發(fā)生比例上減少了四至九成。在結(jié)對(duì)編程的相關(guān)實(shí)驗(yàn)中,使用 TDD 的小組比未使用的小組在黑箱測(cè)試通過(guò)率上平均高出 18%,效果相當(dāng)明顯。
但反觀日常所接觸的實(shí)際工作中,對(duì)于 TDD 的使用并不多見(jiàn),碼農(nóng)們對(duì)其價(jià)值也褒貶不一,網(wǎng)上甚至有大咖寫文章公開(kāi)論述觀點(diǎn)來(lái)反對(duì)它,這又是為什么呢?前不久我剛好在融云的一個(gè)項(xiàng)目里小范圍實(shí)踐了一把,我就試著結(jié)合各方觀點(diǎn),斗膽談?wù)勛约旱捏w會(huì)。
首先介紹一下什么是 TDD ,TDD 最早是由 Kent Beck 提出并在他的《Test-Driven Development By Example》一書中進(jìn)行詳細(xì)闡述的(Kent Beck也是極限編程 Extreme Programming 概念的提出者)。書中所述:TDD 就是以測(cè)試作為開(kāi)發(fā)過(guò)程的中心,要求編寫任何產(chǎn)品代碼之前,首先編寫用于定義產(chǎn)品代碼行為的測(cè)試,而編寫的產(chǎn)品代碼又要以使測(cè)試通過(guò)為目標(biāo)的軟件工程方法,目的是構(gòu)造簡(jiǎn)單、清晰、高質(zhì)量的代碼。
測(cè)試是保證軟件質(zhì)量的重要手段,一個(gè)公司的研發(fā)部門通常都會(huì)有很大比例的測(cè)試團(tuán)隊(duì)作為質(zhì)量保證的堅(jiān)實(shí)后盾。那么作為開(kāi)發(fā)人員是不是就可以只關(guān)心寫代碼的進(jìn)度,編譯通過(guò)后再跑幾遍沒(méi)問(wèn)題就提交代碼呢?這樣的程序在碰上初始條件稍有改變,或者壓力、并發(fā)等外部因素略有變化下會(huì)不會(huì)出現(xiàn)崩潰等問(wèn)題呢?有的團(tuán)隊(duì)會(huì)制定規(guī)章制度,要求給完成的代碼編寫測(cè)試用例,必須通過(guò)才可以提交。這或許能在一定程度上使情況得到改善,但實(shí)際效果真的有那么明顯么?我就聽(tīng)過(guò)有人抱怨說(shuō),完成代碼后再寫測(cè)試用例是浪費(fèi)時(shí)間,因?yàn)閷?duì)邏輯走向已十分了解,測(cè)試自然不會(huì)有什么大的紕漏,有那個(gè)時(shí)間不如多寫幾行代碼,反正有測(cè)試團(tuán)隊(duì)兜底。乍一聽(tīng)似有道理,但仔細(xì)一想實(shí)則是在逃避責(zé)任,也在浪費(fèi)公司資源。
那保證軟件質(zhì)量,有何良策呢? 讓我們來(lái)看 TDD 是如何做的,前面說(shuō)過(guò) TDD 的精髓在于將測(cè)試前置,這看似微不足道的變化到底會(huì)帶來(lái)怎樣的化學(xué)反應(yīng)呢?
第一,能夠明確目的。 在動(dòng)手編寫生產(chǎn)代碼之前,就得先想好這一部分邏輯的輸入輸出是什么,編寫滿足需求的測(cè)試用例,同時(shí)增加對(duì)需求的強(qiáng)化理解。舉個(gè)簡(jiǎn)單的例子,與合作開(kāi)發(fā)的同事對(duì)好需求,劃分好各自實(shí)現(xiàn)的邏輯塊,結(jié)果后續(xù)聯(lián)調(diào)時(shí)才發(fā)現(xiàn)一方理解錯(cuò)了意思,之前的工作量就白費(fèi)了。有了這提前編寫的測(cè)試代碼,就能邏輯層面再次明確目標(biāo),避免語(yǔ)言文字上的誤解。在編寫代碼過(guò)程中,更容易做到心無(wú)旁騖,思緒不會(huì)亂飄,因?yàn)槟愕哪繕?biāo)就是編寫能通過(guò)測(cè)試用例的代碼。當(dāng)然這一過(guò)程可能會(huì)需要持續(xù)對(duì)測(cè)試目標(biāo)進(jìn)行完善,即所謂的 TDD 微循環(huán):測(cè)試 -> 實(shí)現(xiàn) -> 重構(gòu) -> 測(cè)試 ...
第二,強(qiáng)化了模塊與接口的概念。 再紛繁復(fù)雜的業(yè)務(wù)邏輯也能按功能、層級(jí)分為若干業(yè)務(wù)模塊,模塊與模塊之間通過(guò)接口(API)通信,做好這兩點(diǎn)的設(shè)計(jì)無(wú)形中也就降低了業(yè)務(wù)邏輯的耦合性,低耦合又是單元測(cè)試的前提條件。這樣操作等同于迫使開(kāi)發(fā)者將接口的設(shè)計(jì)與低耦合性放在第一位去考慮。工作中在接到項(xiàng)目、明確需求后,如何分配任務(wù)往往非常考驗(yàn)團(tuán)隊(duì)人員的綜合能力,拆解顆粒度太粗容易造成邊界不明確;太細(xì)又牽扯過(guò)多精力,不易實(shí)施。但無(wú)論怎樣,模塊化和明確的接口設(shè)計(jì)是任務(wù)拆解得以順利實(shí)施的前提,對(duì)將來(lái)可能發(fā)生的重構(gòu)也是極好的。
第三,有利于任務(wù)的并行展開(kāi)。 當(dāng)任務(wù)拆解分配到個(gè)人后,必然有些邏輯是需要前提輸入條件的,比如客戶端需要請(qǐng)求服務(wù)器,那么在編寫測(cè)試用例時(shí)就應(yīng)當(dāng)提供這樣的前提條件模擬,即 Mock 對(duì)象。目前流行的測(cè)試框架都帶有 Mock 組件,比如谷歌的 GTest/GMock。有了這些交互對(duì)象,無(wú)論處在業(yè)務(wù)流程的哪個(gè)階段,都可以馬上展開(kāi)任務(wù),隨時(shí)與上下游模塊進(jìn)行聯(lián)調(diào),同時(shí)利用各自的單元測(cè)試劃分 Bug 歸屬。如果這一步不提前進(jìn)行,開(kāi)發(fā)任務(wù)就要按順序進(jìn)行,難免出現(xiàn)人員等待的情況。
第四,可以非常高效地進(jìn)行重構(gòu)。 說(shuō)起重構(gòu),可以說(shuō)稍大點(diǎn)的項(xiàng)目,因?yàn)樾枨蟮淖兓蛘哌壿嫷母拢貥?gòu)在所難免。有些人就會(huì)心里發(fā)怵,生怕會(huì)引入新的 bug,甚至陷入重構(gòu)的泥潭。如果這時(shí)有了事前準(zhǔn)備好的測(cè)試用例,每重構(gòu)完一塊查看一下測(cè)試結(jié)果,就可化風(fēng)險(xiǎn)于無(wú)形。
此外 TDD 還很多優(yōu)點(diǎn),如快速反饋,測(cè)試用例即文檔,降低測(cè)試團(tuán)隊(duì)負(fù)擔(dān)等等。聊完了優(yōu)點(diǎn),接下來(lái)再看看網(wǎng)上大咖們對(duì) TDD 的負(fù)面情緒都有哪些。
目前來(lái)看最集中的抱怨就是 TDD 會(huì)增加時(shí)間的投入。TDD 的優(yōu)勢(shì)是建立在高質(zhì)量測(cè)試用例這一前提下的,如果測(cè)試代碼寫的不夠好或者不夠全面就難以覆蓋所有功能點(diǎn),而"測(cè)試 -> 實(shí)現(xiàn) -> 重構(gòu)" 的微循環(huán)也會(huì)帶來(lái)不少測(cè)試代碼的開(kāi)發(fā)工作。對(duì)于新手來(lái)講,耽誤了時(shí)間,效果卻很有限,自然動(dòng)力不足。即便是有信心能使用好 TDD 的團(tuán)隊(duì),很多時(shí)候它的付出回報(bào)比也依然不高,尤其在強(qiáng)調(diào)迭代速度的互聯(lián)網(wǎng)公司,根據(jù)時(shí)間、質(zhì)量、花費(fèi)三者只能取其二的理論,多數(shù)情況下也只能舍棄一部分質(zhì)量來(lái)保證研發(fā)速度,何況有經(jīng)驗(yàn)的團(tuán)隊(duì)即使不用 TDD 也是能保證質(zhì)量損失在可控范圍內(nèi)的。
其他的抱怨來(lái)自 TDD 的規(guī)則太教條化,比如它的三大定律:
1. 在編寫不能通過(guò)的單元測(cè)試前,不可編寫生產(chǎn)代碼。
2. 只可編寫剛好無(wú)法通過(guò)的單元測(cè)試,不能編譯也算不通過(guò)。
3. 只可編寫剛好滿足以通過(guò)當(dāng)前失敗測(cè)試的生產(chǎn)代碼。
這一點(diǎn)關(guān)鍵看怎么理解,有個(gè)博主說(shuō)的就很好 "Learn the rules, THEN break them." 一旦理解了定律所表達(dá)的真實(shí)含義,是可以根據(jù)實(shí)際情況靈活變通的。
下面來(lái)談?wù)剛€(gè)人對(duì) TDD 一些膚淺的看法。
既然 TDD 是軟件工程的一種理論方法,那么就會(huì)有它的適用范圍,短平快的任務(wù)應(yīng)用空間不會(huì)太大,TDD 更適合一些大中型、需要長(zhǎng)期維護(hù)的項(xiàng)目,最好團(tuán)隊(duì)中能有 TDD 經(jīng)驗(yàn)的人帶領(lǐng),如果沒(méi)有可以去看看網(wǎng)上一些著名的開(kāi)源項(xiàng)目是怎么編寫它的測(cè)試代碼的,學(xué)習(xí)高手寫的代碼,每看一次都非常受益。
另外極限編程的一些理念,比如 KISS (Keep It Simple, Stupid),YAGNI (You Aren't Gonna Need It)與 TDD 結(jié)合起來(lái)也很值得玩味。這也不難理解,XP 和 TDD 本來(lái)就是一個(gè)人提出的理論。無(wú)論寫代碼還是寫測(cè)試用例,這些原則性的東西,最好能貫徹。
最后再說(shuō)一些編程方面的經(jīng)驗(yàn)吧,最近這個(gè)項(xiàng)目給我印象最深的就是斷言的使用。不僅是在測(cè)試用例中用來(lái)判斷結(jié)果與期望值是否相符,更多是要在程序的關(guān)鍵位置埋好斷言,將風(fēng)險(xiǎn)扼殺在搖籃之中。比如開(kāi)源代碼 WebRTC 在核心類的方法中就大量使用了斷言,判斷調(diào)用線程是否正確,關(guān)鍵值是否符合要求等。這類錯(cuò)誤可能會(huì)在程序運(yùn)行到后面某個(gè)點(diǎn)才暴露出來(lái),相同原因?qū)е碌默F(xiàn)象多種多樣,如果不及時(shí)發(fā)現(xiàn),會(huì)大大增加 Debug 的成本。
所以我的建議就是斷言要大膽的加,甚至允許在 Release 版本中的關(guān)鍵位置存在斷言,這樣用戶在反饋問(wèn)題時(shí),就能正確歸因,及時(shí)解決改進(jìn)。
申請(qǐng)創(chuàng)業(yè)報(bào)道,分享創(chuàng)業(yè)好點(diǎn)子。點(diǎn)擊此處,共同探討創(chuàng)業(yè)新機(jī)遇!