TDD生存手冊(cè)

做TDD是為什么?

關(guān)于TDD的概念切蟋、工具统捶、技巧等,經(jīng)典的書(shū)籍材料可能介紹的更為全面細(xì)致柄粹。這篇文章想分享的是從一個(gè)普通開(kāi)發(fā)的角度怎么看待TDD的喘鸟。以及我是怎么從感興趣,到充滿(mǎn)困惑驻右,再到有限的嘗試什黑,直到有一天驀然回首發(fā)現(xiàn)已經(jīng)自然而然的用起了TDD的過(guò)程。希望能對(duì)有著類(lèi)似困惑仍在探索的同學(xué)有所幫助堪夭。
遺憾的是愕把,在開(kāi)始所謂“干貨”以前,首先還是要談?wù)劺砟钌R驗(yàn)槲野l(fā)現(xiàn)這是一個(gè)繞不過(guò)去的問(wèn)題恨豁。
你為什么要使用TDD/寫(xiě)unit test?
不同的人也許有不同的答案:

  1. 因?yàn)檫@是現(xiàn)在流行的爬迟,“正確”的開(kāi)發(fā)方式橘蜜;
  2. 因?yàn)檫@樣寫(xiě)出來(lái)的代碼質(zhì)量更高;
  3. 因?yàn)門(mén)DD和unit test能產(chǎn)生更好的設(shè)計(jì)雕旨;
  4. 因?yàn)槔习逡蟊仨氝_(dá)到xx%的覆蓋率扮匠;
  5. ……

以及一個(gè)派生的問(wèn)題捧请,
如果說(shuō):測(cè)試只能用來(lái)證明bug的存在凡涩,而不能證明程序沒(méi)有bug。
那么:寫(xiě)Unit Test的意義是什么疹蛉?程序員寫(xiě)出的Unit Test與軟件質(zhì)量有什么關(guān)系活箕?

一切不以重構(gòu)為目標(biāo)的單元測(cè)試都是耍流氓

當(dāng)然,這里是指在TDD語(yǔ)境下的單元測(cè)試可款。
在與同道交流TDD經(jīng)驗(yàn)育韩,特別是與測(cè)試人員交流時(shí)克蚂。我們明顯的發(fā)現(xiàn),TDD所說(shuō)的Test筋讨,與測(cè)試人員口中的Test完全不是一回事埃叭。我們甚至討論過(guò)能不能用其它的詞語(yǔ)替換“測(cè)試”或Test,來(lái)避免歧義悉罕。
經(jīng)過(guò)朋友的啟發(fā)和反思自己對(duì)TDD的執(zhí)念的來(lái)源后赤屋,我發(fā)現(xiàn)對(duì)于自己來(lái)說(shuō),TDD中寫(xiě)測(cè)試的真正目的壁袄,是重構(gòu)类早。

  • 我常常會(huì)在讀代碼或?qū)懘a時(shí)產(chǎn)生種種的沖動(dòng):“這是什么鬼”,“我為什么要把生命浪費(fèi)在這種東西上”嗜逻, “一定有更好的辦法”涩僻。
  • 我需要通過(guò)重構(gòu)來(lái)寫(xiě)出更合理的代碼 。
  • 為了安全的重構(gòu)栈顷,我需要測(cè)試逆日。

而與TDD相關(guān)的其它好處,比如文檔化萄凤,將來(lái)作為回歸測(cè)試集屏富,促使開(kāi)發(fā)人員從用戶(hù)角度思考等等,都只是在更高效的改進(jìn)代碼的過(guò)程中附帶產(chǎn)生的蛙卤。

換句話說(shuō)狠半,如果你不準(zhǔn)備在將來(lái)修改代碼,無(wú)論主動(dòng)(重構(gòu))還是被動(dòng)(改bug颤难,加功能)神年,那么寫(xiě)單元測(cè)試對(duì)你完全是浪費(fèi)時(shí)間。
但是話說(shuō)回來(lái)行嗤,如果你真的確信這段代碼永遠(yuǎn)無(wú)需修改已日,那么不要說(shuō)單元測(cè)試,源代碼也是沒(méi)有必要的栅屏。不是么飘千?

回到前面的另一個(gè)問(wèn)題,TDD中的單元測(cè)試與代碼質(zhì)量之間的關(guān)系栈雳。
我的回答是:測(cè)試用例本身不能保證質(zhì)量护奈。
并不是有了更多的測(cè)試數(shù)量,更高的覆蓋比例哥纫,代碼就自然變好了霉旗。如果說(shuō)TDD能提高質(zhì)量,那一定是因?yàn)門(mén)DD給了開(kāi)發(fā)者安全和快速反饋的環(huán)境進(jìn)行重構(gòu),從而幫助開(kāi)發(fā)者不斷改進(jìn)寫(xiě)出更好的代碼厌秒。

打個(gè)比方读拆,同一個(gè)作者,一篇文章是在交稿前半個(gè)小時(shí)趕著寫(xiě)完鸵闪,錯(cuò)別字都沒(méi)改就發(fā)出來(lái)的檐晕;另一篇發(fā)布以前斟酌再三,幾易其稿蚌讼。哪一篇的質(zhì)量會(huì)更高一些呢棉姐?
答案是顯而易見(jiàn)的吧。不過(guò)請(qǐng)?jiān)傧胍幌肜材妫瑢?xiě)代碼是與寫(xiě)文章的相似程度有多少伞矩?代碼真的是越改質(zhì)量越高么?

反脆弱的代碼

“反脆弱”是《反脆弱》這本書(shū)的作者生造的一個(gè)詞夏志。描述的是脆弱的反面乃坤,一種我們都知道卻沒(méi)有名稱(chēng)的性質(zhì)。一般我們認(rèn)為脆弱的反面是堅(jiān)固沟蔑,然而堅(jiān)固僅僅是對(duì)外部變化不敏感湿诊。反脆弱指的是具有這種性質(zhì)的東西可以從外部變化中獲利,正如同脆弱的東西會(huì)被外部變化損害一樣瘦材。

對(duì)大部分的程序員而言厅须,變化是個(gè)不受歡迎的詞。在我們談?wù)摻训拇a食棕,合理的設(shè)計(jì)時(shí)朗和,針對(duì)的假想敵就是未來(lái)的變化。關(guān)于將來(lái)的變化簿晓,我們能想到的最好結(jié)果不過(guò)是不要搞砸現(xiàn)在設(shè)計(jì)好的一切眶拉。

換句話說(shuō),我們追求的是堅(jiān)固的代碼憔儿,歷經(jīng)變化的侵蝕屹立不倒忆植。
那么,有沒(méi)有反脆弱的代碼谒臼,在變化的滋養(yǎng)中生長(zhǎng)壯大呢朝刊?

對(duì)待可能的變化,不外乎三種態(tài)度:

  1. 這段代碼不打算在將來(lái)再被利用了蜈缤,所以完全不用考慮改變拾氓。
    這不失為一種實(shí)用的態(tài)度。但是現(xiàn)實(shí)中這樣的情況太少劫樟。
  2. 現(xiàn)在寫(xiě)出一個(gè)完美的設(shè)計(jì)痪枫,為所有可能的改變做好準(zhǔn)備,這樣將來(lái)就不會(huì)改變了叠艳。
    但是這是可望而不可即的目標(biāo)奶陈。暫且不論需求變更等不受我們控制的外部變化。僅僅就開(kāi)發(fā)者自己而言附较,往往不論我們今天作出多少努力吃粒,隨著我們?cè)诮鉀Q問(wèn)題過(guò)程中的成長(zhǎng),在明天總是會(huì)遺憾當(dāng)初自己沒(méi)有作出更好的選擇拒课。
  3. 為改變做好準(zhǔn)備徐勃,并且主動(dòng)地,時(shí)時(shí)刻刻地進(jìn)行改變早像。
    這就是TDD的選擇僻肖,可靠地對(duì)代碼進(jìn)行改變。并在這種改變中不斷改善卢鹦。

對(duì)于不熟悉的人而言臀脏,初看起來(lái),TDD最大的特點(diǎn)是寫(xiě)測(cè)試冀自,并且是在實(shí)現(xiàn)代碼之前寫(xiě)測(cè)試這個(gè)反直覺(jué)的實(shí)踐揉稚。卻往往忽略了藏在后面重構(gòu)的那一步。事實(shí)上熬粗,前兩步的紅燈搀玖、綠燈,都是在為第三步的重構(gòu)做準(zhǔn)備驻呐。
第一步灌诅,寫(xiě)出失敗的測(cè)試。是在為即將發(fā)生的重構(gòu)建起保護(hù)網(wǎng)含末。
第二步延塑,盡快的綠燈通過(guò)。是刻意寫(xiě)出需要重構(gòu)的代碼答渔。
既然認(rèn)為改變是有潛在破壞性的关带,那就盡早地、盡可能頻繁地去改變代碼沼撕。
測(cè)試與重構(gòu)宋雏,像是硬幣的兩面一樣,密不可分务豺。

所以磨总,如果你仍然認(rèn)為重構(gòu)像是吃完飯要洗碗一樣的必要但是附屬性的工作。如果你還沒(méi)有感受到TDD帶給你的保護(hù)與自由笼沥,讓你放下對(duì)改變的恐懼蚪燕,心安理得的寫(xiě)下將來(lái)必然會(huì)被改掉的代碼娶牌。那么就算你按照三步循環(huán)去寫(xiě)代碼,恐怕也難以從中獲得益處馆纳。很快就退回盡量預(yù)先思索诗良,想小步卻慢不下來(lái)的老路上。

何謂持續(xù)

事實(shí)上鲁驶,任何一個(gè)老練的程序員必然都有自己的一套方法來(lái)反復(fù)驗(yàn)證和調(diào)整開(kāi)發(fā)中的代碼鉴裹。這些方法可能包括,可控環(huán)境下的調(diào)試钥弯,添加一個(gè)臨時(shí)的main方法作為實(shí)驗(yàn)入口径荔,把代碼片段復(fù)制到外部環(huán)境進(jìn)行驗(yàn)證等等。TDD中的增量開(kāi)發(fā)脆霎、小步快跑总处,用這些方法也可以做到。
我想這大概就是為什么有人會(huì)提出其實(shí)人人都在做TDD吧睛蛛。盡管我不是特別認(rèn)同這種說(shuō)法辨泳。

如果沒(méi)有那個(gè)點(diǎn)的話,也許做不做TDD確實(shí)無(wú)所謂玖院。

這個(gè)點(diǎn)有時(shí)候叫做交付菠红,也可能叫集成、發(fā)布难菌;甚至有時(shí)候并沒(méi)有一個(gè)清晰的事件點(diǎn)试溯,不過(guò)是寫(xiě)完放下,過(guò)了幾個(gè)星期而已郊酒。但是這個(gè)點(diǎn)是實(shí)實(shí)在在存在的遇绞,它就是“鮮活”代碼和遺留代碼的分界點(diǎn)。越過(guò)了這一點(diǎn)燎窘,你手中的代碼就會(huì)搖身一變摹闽,從那個(gè)開(kāi)朗敏捷的少年,變做陰郁固執(zhí)喜怒無(wú)常的怪獸褐健。

維護(hù)期

TDD的獨(dú)特之處付鹿,是讓測(cè)試伴隨代碼從生到死的整個(gè)生命周期,始終為代碼變化提供保護(hù)網(wǎng)蚜迅,讓代碼的“保鮮期”盡可能的長(zhǎng)舵匾,抹平這個(gè)轉(zhuǎn)變的節(jié)點(diǎn)。
現(xiàn)在持續(xù)集成谁不、持續(xù)交付的概念已經(jīng)是主流了坐梯。但是什么是持續(xù)呢?個(gè)人淺見(jiàn)刹帕,不是說(shuō)設(shè)置了一個(gè)服務(wù)器吵血,定時(shí)跑幾個(gè)任務(wù)就是持續(xù)了谎替。而是不再有那個(gè)代碼保鮮期的拐點(diǎn),可以一直平滑的發(fā)展下去蹋辅。
TDD無(wú)疑是它的重要保證環(huán)節(jié)钱贯。

開(kāi)發(fā)者體驗(yàn)

TDD的好處有哪些?關(guān)于這個(gè)問(wèn)題晕翠,我原來(lái)總是嘗試從客觀的角度來(lái)回答喷舀。比如質(zhì)量砍濒,比如可維護(hù)性淋肾,比如鼓勵(lì)好的設(shè)計(jì)等等“中希總之樊卓,就是剔除了人的因素。
然而杠河,當(dāng)我認(rèn)真探索自己這一路走來(lái)的歷程碌尔。是怎么自此在TDD上略有心得后情不自禁的在社區(qū)分享。重新開(kāi)始寫(xiě)博客券敌,幾乎每篇都是關(guān)于TDD唾戚。自發(fā)地在公司里組織編程道場(chǎng)(Dojo)推廣TDD。背后的動(dòng)力其實(shí)很簡(jiǎn)單待诅,這樣開(kāi)發(fā)讓我很爽叹坦。

TDD inside

這個(gè)答案聽(tīng)起來(lái)實(shí)在太不正式,好像也沒(méi)啥說(shuō)服力卑雁。但的確是我的真實(shí)想法募书。
有人可能會(huì)說(shuō):工作嘛哪能那么理想化,老板給你工資就行了测蹲,誰(shuí)管你開(kāi)心不開(kāi)心莹捡。
且不說(shuō)更開(kāi)心的程序員應(yīng)該效率更高,而且開(kāi)心本身就是公司狀況良好的體現(xiàn)之類(lèi)的客觀化的理由扣甲。
從開(kāi)發(fā)者個(gè)人而言篮赢,就算僅僅為了心情愉快、延年益壽琉挖,也是值得去做些努力去改進(jìn)代碼的荷逞。因?yàn)楦纳拼a質(zhì)量和開(kāi)發(fā)流程,本身就是改善工作環(huán)境粹排。
最近恰好讀了一篇研究程序員各種不爽的論文种远。其中統(tǒng)計(jì)了上千個(gè)程序員的答卷。對(duì)工作中的不爽進(jìn)行了分類(lèi)顽耳。
可以看到盡管工作中有不少不受我們控制的部分坠敷,比如人的原因(416個(gè))和公司流程(544個(gè))妙同,但是最大的一部分還是來(lái)源于代碼相關(guān)問(wèn)題(788個(gè))

再來(lái)看看常見(jiàn)的程序員不爽原因。前三位里有兩個(gè)是:

  • 解決問(wèn)題被卡住膝迎。
  • 糟糕的代碼質(zhì)量以及代碼習(xí)慣粥帚。
另一個(gè)是時(shí)間壓力

而這些都是可以通過(guò)開(kāi)發(fā)者自己努力來(lái)改善的。我的切身感受限次,TDD帶給了我如下變化:

  • 交付代碼的時(shí)候充滿(mǎn)了信心芒涡。
  • 從測(cè)試或者客戶(hù)那里得到意外的錯(cuò)誤后,不是感覺(jué)恐慌卖漫,而是回顧一遍測(cè)試费尽,往往已經(jīng)能定位到原因了。
  • 幾乎從不調(diào)試程序羊始。
  • 要修改遺留代碼旱幼,對(duì)質(zhì)量又不滿(mǎn)意的時(shí)候,不再一邊忍耐一邊抱怨突委。因?yàn)槲倚睦锖芮宄芈保夷芸煽康母牡羲灰斜匾@么做匀油。

我想這大概就是TDD為什么給我?guī)?lái)這么大幸福感的原因吧缘缚。

成長(zhǎng)路徑

下面我結(jié)合個(gè)人體驗(yàn)寫(xiě)一下從初識(shí)TDD,到實(shí)做中得心應(yīng)手的過(guò)程敌蚜,希望能有所幫助桥滨。

著土

掌握最基本的,讓TDD成為可能的技術(shù)钝侠。比如:什么是單元測(cè)試该园,如何在不同環(huán)境下運(yùn)行單元測(cè)試,有哪些可選的框架等等帅韧。
在網(wǎng)絡(luò)時(shí)代里初,這個(gè)階段應(yīng)該是最容易的,各種資源和教程觸手可及忽舟。另外隨著業(yè)界對(duì)測(cè)試越來(lái)越重視双妨,較新的語(yǔ)言、框架叮阅、平臺(tái)都把測(cè)試作為標(biāo)配提供支持刁品。所以這個(gè)階段應(yīng)該很容易就能度過(guò)。

出芽

嘗試使用TDD做一些簡(jiǎn)單程序浩姥。體會(huì)紅燈挑随、綠燈、重構(gòu)的循環(huán)過(guò)程勒叠。

本階段往往有兩個(gè)結(jié)果兜挨,一種是試了試完全摸不著頭腦膏孟;另一種是試了試非常好用,然后拿去實(shí)用發(fā)現(xiàn)完全不是那么回事拌汇。
正像前面提到的TDD最重要的不是表面上的三步循環(huán)柒桑,而是轉(zhuǎn)變寫(xiě)程序的思路。如果你扔懷著對(duì)修改代碼的恐懼噪舀,依賴(lài)于現(xiàn)在”想清楚“魁淳,那么先寫(xiě)測(cè)試并不會(huì)幫到你多少。這更像是學(xué)習(xí)騎自行車(chē)或游泳一樣与倡,僅僅理解并沒(méi)太大用處界逛,需要一個(gè)過(guò)程去體會(huì)和掌握。

本階段可以說(shuō)是一大難點(diǎn)蒸走,很多人可能就是在這里覺(jué)得TDD可望不可即仇奶,或者僅僅是看起來(lái)很美貌嫡。下面是我的一些建議比驻。

  1. 一開(kāi)始可以亦步亦趨根據(jù)教程示例做一遍。但是之后一定要找一個(gè)沒(méi)有做過(guò)的題目嘗試自己解決岛抄。
  2. 不宜選擇簡(jiǎn)單到你一下就可以在腦子里寫(xiě)出偽代碼的問(wèn)題别惦,但是也不要選過(guò)于復(fù)雜的問(wèn)題。練習(xí)常用的Kata是個(gè)不錯(cuò)的選擇夫椭。詳情見(jiàn)后面的Kata介紹掸掸。
  3. 很有可能?chē)L試了卻沒(méi)有成功,別擔(dān)心這是正常的蹭秋。如果你練習(xí)的是熟知的Kata的話扰付,可以在網(wǎng)上找找別人解的過(guò)程,很多都是有視頻的仁讨∮疠海看完有心得了以后再做一次。
  4. “裝傻”是本階段的一個(gè)技巧洞豁。因?yàn)槟阋呀?jīng)有了一套如何解決問(wèn)題的方法盐固,在轉(zhuǎn)換到新的做法的中間過(guò)程里,往往不自覺(jué)的用原有的信念來(lái)評(píng)判新的做法丈挟。這時(shí)需要靠裝傻來(lái)暫時(shí)放下已有的東西刁卜。學(xué)習(xí)的時(shí)候不妨把它作為一項(xiàng)挑戰(zhàn),看看自己能寫(xiě)出多傻的代碼曙咽,能用多慢的節(jié)奏達(dá)到目標(biāo)蛔趴。
  5. “一次一個(gè)問(wèn)題”是另一個(gè)需要練習(xí)才能掌握的技巧。嘗試在循環(huán)的每一步只關(guān)注于一個(gè)問(wèn)題:測(cè)試代碼例朱、實(shí)現(xiàn)功能孝情、或是改善設(shè)計(jì)之拨。
    這個(gè)建議也適用于更高層面的問(wèn)題。比如咧叭,在練習(xí)的時(shí)候不要去擔(dān)心諸如:“這樣性能太差了”蚀乔,或者“如果我每段代碼都花這么長(zhǎng)時(shí)間寫(xiě)測(cè)試,明天老板就會(huì)炒了我”這樣的問(wèn)題菲茬。
  6. 如果你不把自己限定為一個(gè)“Java程序員”或“PHP程序員”吉挣,可以考慮用一種不熟悉的語(yǔ)言結(jié)合TDD來(lái)解決某個(gè)熟悉的問(wèn)題。在重拾初學(xué)者身份后婉弹,往往會(huì)意識(shí)到一個(gè)看似簡(jiǎn)單的問(wèn)題在解決過(guò)程中有多少需要搞清楚的地方睬魂,更容易體會(huì)到TDD的方式在這個(gè)過(guò)程中所起的作用。
  7. 事實(shí)上這個(gè)階段實(shí)在有點(diǎn)挑戰(zhàn)镀赌,我建議最好找人一起練習(xí)氯哮。代碼道場(chǎng)(Dojo)和代碼靜修(Code Retreat)是很好的練習(xí)活動(dòng)。如果有機(jī)會(huì)可以考慮參加商佛。關(guān)于代碼道場(chǎng)喉钢,可以看看這位同學(xué)的筆記
    當(dāng)然很可能你在周邊找不到這樣的活動(dòng),但是又很想?yún)⒓恿寄贰肠虽?梢钥紤]自己組織,沒(méi)錯(cuò)我是認(rèn)真的玛追。從中你會(huì)獲得更多意外的收獲税课。

生根

如果你在上個(gè)階段獲得了收獲,對(duì)TDD方法有了足夠的信心痊剖。這時(shí)就可以開(kāi)始考慮在工作中玩真格的了韩玩。
如果在上個(gè)階段學(xué)到的夠多,那么用在工作中并不是很困難的一件事陆馁。但是找颓,還是有很多的坑要注意,畢竟這不再是自己搗鼓了氮惯。

  • 最好選擇新增的叮雳,相對(duì)較為獨(dú)立的模塊開(kāi)始嘗試。
    一方面這是因?yàn)榭梢员荛_(kāi)很多技術(shù)上的難點(diǎn)妇汗,更重要的是因?yàn)檫@種代碼涉及的人比較少帘不。相對(duì)而言更不容易受到阻力。
    可能你會(huì)覺(jué)得日常工作中更多的是修改老代碼杨箭,并沒(méi)有多少機(jī)會(huì)新增一塊寞焙。是的,所以一定要珍惜這樣的機(jī)會(huì)啊捣郊!每當(dāng)我看到已經(jīng)有了足夠能力的程序員在寫(xiě)嶄新的代碼時(shí)辽狈,卻沒(méi)有為它配上足夠的測(cè)試保護(hù),任由它慢慢的變得混亂脆弱呛牲」蚊龋總是無(wú)比的惋惜。
  • 如果的確沒(méi)有新模塊的機(jī)會(huì)娘扩,可以把比較基礎(chǔ)的代碼着茸,比如工具類(lèi)的部分進(jìn)行抽取,用單元測(cè)試圍起來(lái)琐旁,然后進(jìn)行重構(gòu)也是不錯(cuò)的涮阔。
  • 一個(gè)常見(jiàn)的困難是感覺(jué)采用了TDD后進(jìn)度慢了很多,擔(dān)心領(lǐng)導(dǎo)或者老板不答應(yīng)灰殴。
    這還真不是個(gè)簡(jiǎn)單問(wèn)題:
    • 首先敬特,要區(qū)分真的進(jìn)度慢了,還是感覺(jué)進(jìn)度慢了牺陶。有些時(shí)候在壓力之下伟阔,我們往往是自欺欺人的估算一個(gè)“理想情況”下的進(jìn)度,然后假定真的能趕上义图。如果是這種情況减俏,實(shí)打?qū)嵉膶?xiě)出測(cè)試來(lái)更有利于做出現(xiàn)實(shí)的估算召烂。雖然拿到任務(wù)的第一天就說(shuō)會(huì)延期很難說(shuō)出口碱工,我認(rèn)為還是要比最后一天再說(shuō)要好一些。
    • 有可能是因?yàn)閮H僅關(guān)注在“開(kāi)發(fā)”的進(jìn)度上奏夫,卻沒(méi)有考慮在調(diào)試和測(cè)試階段省下的時(shí)間怕篷。如果有這樣的壓力,可以先在不引起太大抵觸的范圍內(nèi)采用TDD酗昼,并且關(guān)注是否在后續(xù)的階段大幅提高了效率廊谓。如果的確有效果,相信大家會(huì)越來(lái)越理解和接受麻削;如果毫無(wú)效果蒸痹,那可能要反省一下是不是哪里做的有問(wèn)題了。
    • 學(xué)習(xí)新的方法是需要一個(gè)過(guò)程的呛哟。這也是為什么在上個(gè)階段特別提出要做專(zhuān)門(mén)練習(xí)的原因叠荠。如果公司和領(lǐng)導(dǎo)并不是特別給你支持,而你又真的希望通過(guò)掌握新方法來(lái)提高扫责。那可能還是需要自己在工作之外做些努力來(lái)度過(guò)這個(gè)階段榛鼎。
  • 在壓力之下人總是會(huì)傾向于采用熟悉的方法。哪怕明知道最后會(huì)搞得一團(tuán)糟也還是這樣,畢竟那一團(tuán)糟是自己熟悉的一團(tuán)糟者娱。
    因此實(shí)做中發(fā)現(xiàn)沒(méi)有練習(xí)中那么行云流水是很正常的抡笼。給自己定下實(shí)際的期望值,逐步提高黄鳍。比如:
    • 寫(xiě)了這么多代碼推姻,至少要有一個(gè)測(cè)試。
    • 我寫(xiě)的每句代碼在交付前至少都用測(cè)試驗(yàn)證過(guò)框沟。
    • 每次我都先試試先寫(xiě)個(gè)測(cè)試小步前進(jìn)拾碌,實(shí)在不行了再退回原來(lái)的方式

在實(shí)際工作中發(fā)現(xiàn)退回老路,建議抽出專(zhuān)門(mén)的時(shí)間按照上個(gè)階段的方式繼續(xù)練習(xí)街望。我在學(xué)習(xí)TDD的過(guò)程中的最大附帶收獲就是養(yǎng)成了練習(xí)的習(xí)慣校翔。
可能很多程序員聽(tīng)到練習(xí)兩個(gè)字就煩。畢竟懶惰是程序員的一大美德嘛灾前。我們是腦力工作者又不是搬磚防症。練那么熟、記那么多東西又有什么用呢哎甲?總還是比不過(guò)自動(dòng)化的程序和搜索引擎蔫敲。
的確是這樣的。不過(guò)練習(xí)的目的不是超過(guò)程序和搜索引擎炭玫,而是遷就我們大腦有限的運(yùn)算量奈嘿。只有熟練到一定程度,大腦才可以不再疲于應(yīng)對(duì)各種細(xì)節(jié)吞加,有空去關(guān)心真正重要的問(wèn)題裙犹。在改變的過(guò)程中這一點(diǎn)非常重要。

破土

隨著越來(lái)越多的使用新方法衔憨,自然而然地會(huì)想把它推廣到更大的范圍叶圃。這時(shí)就要面對(duì)遺留代碼這塊硬骨頭了。
假如你是團(tuán)隊(duì)中最早采用TDD的人践图,很可能碰到很多沒(méi)有測(cè)試掺冠,而且難以測(cè)試的代碼。
這里一定要隆重介紹《修改代碼的藝術(shù)》(Working Effectively with Legacy Code)码党。在這個(gè)階段我曾經(jīng)疑惑了很久德崭,陷入了一個(gè)無(wú)解的死循環(huán)里,多虧了這本書(shū)的點(diǎn)撥才得以突破揖盘。
這個(gè)無(wú)解的問(wèn)題是這樣的:

  1. 代碼好爛眉厨,想要重構(gòu);
  2. 為了重構(gòu)扣讼,需要寫(xiě)測(cè)試缺猛;
  3. 代碼好爛,沒(méi)法測(cè)試,先要重構(gòu)荔燎;
  4. 為了重構(gòu)耻姥,需要寫(xiě)測(cè)試;
  5. ……

破解的方法嘛有咨,其實(shí)說(shuō)來(lái)很簡(jiǎn)單琐簇。以最少的代價(jià)邁出第一步,在沒(méi)有測(cè)試保護(hù)的情況下進(jìn)行重構(gòu)座享,為后續(xù)有序的循環(huán)打開(kāi)大門(mén)婉商。
具體的手法和技巧,這本書(shū)里講的非常好了渣叛。建議帶著問(wèn)題去讀丈秩,一定收獲滿(mǎn)滿(mǎn)。
需要注意的是淳衙,有些時(shí)候?yàn)榱嗽诎褰Y(jié)的陳舊代碼上敲開(kāi)一條縫蘑秽,必須要采用一些不是那么“最佳實(shí)踐”的方式。比如放寬可見(jiàn)性箫攀,取消final限制等等肠牲。這些做法很有可能會(huì)遭到反對(duì)。最極端的情況下靴跛,為了方便測(cè)試修改哪怕一行代碼缀雳,有些人都會(huì)覺(jué)得是荒謬的。
這時(shí)候反復(fù)爭(zhēng)論是沒(méi)有太大意義的梢睛。反對(duì)者有他們正當(dāng)?shù)睦碛煞视 U缜懊嬲劦降膱?jiān)固與反脆弱的代碼的兩種心態(tài)。他們僅僅把這種改變看作千里大堤上的一個(gè)蟻穴扬绪,還看不到在將來(lái)的改善中能帶來(lái)的收益竖独。所以,重要的不是誰(shuí)說(shuō)服誰(shuí)挤牛,而是做出實(shí)效。首先表明自己的做法种蘸,在互相可接受的限度內(nèi)去做墓赴。
有一點(diǎn)特別特別要注意:不要用PowerMock之類(lèi)的“黑魔法“去遷就代碼,費(fèi)盡心力只是為了避免因?yàn)榧訙y(cè)試而修改代碼航瞭。別忘了诫硕,寫(xiě)測(cè)試的目的是圈起一塊領(lǐng)地來(lái)馴服遺留代碼,而不是把測(cè)試當(dāng)作一層粉飾去貼在代碼之上刊侯。

成材

上個(gè)階段可以說(shuō)是一個(gè)分水嶺章办,就像學(xué)游泳學(xué)會(huì)踩水,一旦掌握就“淹不死”了。到了這個(gè)階段你應(yīng)該已經(jīng)很有信心的在各種場(chǎng)合使用TDD了藕届。后面主要考慮的是如何更加高效的使用這種方法挪蹭,怎么帶動(dòng)更多的人。
這個(gè)階段我也還在路上休偶,只能說(shuō)說(shuō)我觀察到的大規(guī)模的推動(dòng)TDD中可能會(huì)碰到的一些坑梁厉。

  1. 小心Mock濫用。Mock踏兜,包括相當(dāng)一部分的Stub词顾,應(yīng)該用來(lái)表達(dá)對(duì)象間的職責(zé)。而不是模擬不必要的實(shí)現(xiàn)細(xì)節(jié)碱妆。
  2. 避免深的測(cè)試類(lèi)繼承結(jié)構(gòu)肉盹。極端情況就是“雙樹(shù)結(jié)構(gòu)”,測(cè)試類(lèi)將生產(chǎn)代碼的類(lèi)結(jié)構(gòu)依樣畫(huà)葫蘆又做了一遍疹尾。其實(shí)我的個(gè)人看法是測(cè)試類(lèi)和測(cè)試幫助類(lèi)都根本不應(yīng)該出現(xiàn)繼承垮媒。
  3. 不要過(guò)于執(zhí)著完全的、絕對(duì)一致的方法論航棱。

這可以說(shuō)是程序員的職業(yè)病睡雇,無(wú)論什么方法聽(tīng)到的第一反應(yīng)是找反例,即使一萬(wàn)個(gè)場(chǎng)合有用饮醇,只要一個(gè)場(chǎng)合不行它抱,立即就覺(jué)得這是個(gè)無(wú)效的方法。
對(duì)于寫(xiě)程序這可能是很好的習(xí)慣朴艰,畢竟一個(gè)萬(wàn)分之一機(jī)率崩潰的軟件基本上是沒(méi)用的观蓄。但是人不同于機(jī)器,并不會(huì)碰到一個(gè)方法論不能解釋的情況就進(jìn)入死循環(huán)祠墅。80%情況下好用的方法就已經(jīng)很有幫助了侮穿。
這種心態(tài)的另一面,是一旦相信了一種方法毁嗦,就認(rèn)定它必須100%貫徹到每個(gè)角落亲茅。
特別是在剛剛開(kāi)始進(jìn)入這一階段的時(shí)候,很容易雄心勃勃的規(guī)劃一個(gè)嶄新的版圖狗准,一套絕對(duì)化的規(guī)則來(lái)改天換地腔长。
為什么不要這么做您没?

  • 往往缺乏投入產(chǎn)出比喻犁,為了寫(xiě)測(cè)試而寫(xiě)測(cè)試碌廓,花費(fèi)大量精力在已死的代碼或等死的代碼上。
  • 在團(tuán)隊(duì)和組織中對(duì)TDD有疑慮的情況下徒增反對(duì)的可能。
  • 規(guī)劃大烤蜕,見(jiàn)效慢橱鹏,有違小步快跑的精神贮勃。
  • 將干巴巴的規(guī)則凌駕于活生生的個(gè)例之上泉孩,實(shí)際上是期待自己的道理能一勞永逸的解決所有問(wèn)題的懶惰思維珍昨。更重要的是堵塞了將來(lái)進(jìn)一步改進(jìn)的機(jī)會(huì)。

幾個(gè)啟發(fā)性的問(wèn)題:

  1. 一個(gè)測(cè)試從寫(xiě)好以后就再也沒(méi)有失敗過(guò)句喷,說(shuō)明它非常有效還是完全無(wú)用镣典?
  2. 看看你最新寫(xiě)的測(cè)試,什么時(shí)候可以安全的刪掉它唾琼?到了那個(gè)時(shí)候兄春,如果是另一個(gè)程序員維護(hù),他有沒(méi)有信心刪除锡溯?
  3. 回顧最新一次TDD的過(guò)程赶舆,能不能用更少的測(cè)試達(dá)到同樣的信心級(jí)別?

附錄

一些Kata題目

  • FizzBuzz:由于問(wèn)題非常簡(jiǎn)單祭饭。適合用來(lái)講解TDD的概念芜茵。這樣學(xué)習(xí)者的注意力可以全部集中在流程和方法上。但也是因?yàn)閱?wèn)題太過(guò)簡(jiǎn)單倡蝙,不適合自己拿來(lái)練習(xí)如何用TDD解決問(wèn)題九串。
  • 因數(shù)分解:來(lái)自Uncle Bob的題目和解題過(guò)程,很好的展示了TDD如何超出預(yù)期簡(jiǎn)單地解決這個(gè)問(wèn)題悠咱。
  • 羅馬數(shù)字:有一定復(fù)雜度的題目蒸辆。適合用來(lái)練習(xí)如何分解問(wèn)題,以及怎么通過(guò)重構(gòu)簡(jiǎn)化代碼析既。
  • 網(wǎng)球記分:對(duì)于不熟悉業(yè)務(wù)規(guī)則的人需要花一點(diǎn)時(shí)間搞清楚邏輯躬贡。問(wèn)題本身較為簡(jiǎn)單但是繁瑣。適合用來(lái)練習(xí)如何對(duì)付if套if的代碼眼坏。
  • String Calculator:練習(xí)需求不斷變更的情況下如何寫(xiě)代碼拂玻。一定要老老實(shí)實(shí)按照題目要求做一步再看下一步。
  • LCDBank OCR:兩個(gè)題目有類(lèi)似的地方宰译,比較適合練習(xí)如何分解單一職責(zé)檐蚜。
  • 生命游戲:經(jīng)典的題目,對(duì)于如何設(shè)計(jì)測(cè)試用例和順序較有挑戰(zhàn)沿侈。
  • 哈利波特:偏算法闯第,有一定的難度。

網(wǎng)絡(luò)資源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末勾效,一起剝皮案震驚了整個(gè)濱河市嘹悼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌层宫,老刑警劉巖杨伙,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異卒密,居然都是意外死亡缀台,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)哮奇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)膛腐,“玉大人,你說(shuō)我怎么就攤上這事鼎俘≌苌恚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵贸伐,是天一觀的道長(zhǎng)勘天。 經(jīng)常有香客問(wèn)我,道長(zhǎng)捉邢,這世上最難降的妖魔是什么脯丝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮伏伐,結(jié)果婚禮上宠进,老公的妹妹穿的比我還像新娘。我一直安慰自己藐翎,他們只是感情好材蹬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著吝镣,像睡著了一般堤器。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上末贾,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天闸溃,我揣著相機(jī)與錄音,去河邊找鬼。 笑死圈暗,一個(gè)胖子當(dāng)著我的面吹牛掂为,可吹牛的內(nèi)容都是我干的裕膀。 我是一名探鬼主播员串,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昼扛!你這毒婦竟也來(lái)了寸齐?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抄谐,失蹤者是張志新(化名)和其女友劉穎渺鹦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蛹含,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毅厚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浦箱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吸耿。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛹疯,我是刑警寧澤鲸睛,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站疲迂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜糕珊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毅糟。 院中可真熱鬧红选,春花似錦、人聲如沸留特。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蜕青。三九已至苟蹈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間右核,已是汗流浹背慧脱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贺喝,地道東北人菱鸥。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓宗兼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親氮采。 傳聞我的和親對(duì)象是個(gè)殘疾皇子殷绍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容