
TDD是個(gè)好東西,推廣時(shí),需從Why/what/how來說服別人,結(jié)合最近看的一些資料,來說明一下問題:
1、單元測試的理由
《[高效程序員的45個(gè)習(xí)慣:敏捷開發(fā)修煉之道》中已經(jīng)對于單元測試好處做了一些說明:
- 單元測試能及時(shí)提供反饋。你的代碼會重復(fù)得到鍛煉。若修改或者重構(gòu),作為防護(hù)網(wǎng)會檢查是否破外了已有功能。快速得到反饋,并容易修復(fù)他。他是重構(gòu)的基礎(chǔ)。
- 單元測試讓你的代碼更健壯。測試幫助你全面的思考代碼的行為,幫你聯(lián)系正面,反面以及異常情況。
- 單元測試是有用的設(shè)計(jì)工具。有助于簡單設(shè)計(jì),注重實(shí)效。
- 單元測試是讓你自信的后臺。你測試代碼,了解不同條件下的行為,面對壓力找到自信。
- 單元測試解決問題的探測器。提供發(fā)現(xiàn)問題和解決問題的方法。
- 單元測試是可信的文檔。 學(xué)API時(shí),可以幫你提供精準(zhǔn)和可靠的文檔。是程序員之間的語言。
- 單元測試學(xué)習(xí)工具。幫你了解API行為。
2、TDD的風(fēng)格
參考文章《TDD 真的能帶來好設(shè)計(jì)么?》(原文:http://codurance.com/2015/05/12/does-tdd-lead-to-good-design/)中介紹TDD有兩種主要的風(fēng)格,它們在何時(shí)進(jìn)行設(shè)計(jì)有著相當(dāng)顯著的區(qū)別。
2.1 古典派
古典派是由Kent Beck開創(chuàng)的原本風(fēng)格,也被稱為底特律式TDD
2.1.1 主要特點(diǎn)
- 設(shè)計(jì)在重構(gòu)階段發(fā)生。
- 一般來說測試是基于狀態(tài)的測試。
- 在重構(gòu)階段,由測試驅(qū)動的單元可能會分化為多個(gè)類。
- 極少使用Mock技術(shù),除非是要與現(xiàn)存系統(tǒng)隔離。
- 在編碼前不進(jìn)行設(shè)計(jì)方面的思索。設(shè)計(jì)完全是從代碼中浮現(xiàn)出來的。
- 是避免過度設(shè)計(jì)的極佳方法。
- 基于狀態(tài)的測試以及無前置設(shè)計(jì),使得這種風(fēng)格更容易理解和采用。
- 常常結(jié)合簡單設(shè)計(jì)四規(guī)則一起使用。
- 當(dāng)我們知道輸入和期望的輸出,但是不清楚實(shí)現(xiàn)可能是什么樣的時(shí)候,很適合使用這種方式進(jìn)行探索。
- 在我們無法借助行業(yè)專家和行業(yè)語言(比如數(shù)據(jù)轉(zhuǎn)換,算法等)的情況下很有幫助。
2.1.2 問題
- 單純?yōu)榱藴y試暴露狀態(tài)。
- 相比由外而內(nèi)風(fēng)格,重構(gòu)階段通常更大。后面會詳細(xì)介紹由外而內(nèi)風(fēng)格。
- 當(dāng)新的類在重構(gòu)階段浮現(xiàn)出類時(shí),被測的單元就會超出一個(gè)類。如果我們單單看那個(gè)測試的話,這沒什么問題,然而,當(dāng)新的類浮現(xiàn)出來后,它就有了自己的生命。它會被程序的其他部分復(fù)用。當(dāng)這個(gè)類演化時(shí),可能會破壞其它不相關(guān)的測試,因?yàn)槟切y試使用了它們實(shí)際的實(shí)現(xiàn)而不是mock對象。
- 經(jīng)驗(yàn)不足的練習(xí)者往往會跳過重構(gòu),也就是設(shè)計(jì)改進(jìn)的階段,導(dǎo)致開發(fā)進(jìn)程變?yōu)?紅燈—綠燈—紅燈—綠燈—...—紅燈—綠燈—大重構(gòu)。
- 由于探索式的特點(diǎn),測試驅(qū)動下的類產(chǎn)生于“我想我們需要一個(gè)有這樣接口的類”??赡軣o法和系統(tǒng)的其它部分很好地對接。
- 有可能會緩慢費(fèi)事。我們常常一開始已經(jīng)知道被測的類不應(yīng)該有這么多的職責(zé),古典派的建議是等待重構(gòu)階段,只在確實(shí)有必要時(shí)才抽出其它類。對于初學(xué)者來說這可能是個(gè)好建議,對更有經(jīng)驗(yàn)的程序員純粹是浪費(fèi)時(shí)間。
2.2 由外而內(nèi)
由外而內(nèi)TDD,也被稱作倫敦派或者mock式,是由一些XP實(shí)踐先驅(qū)接受和發(fā)展出的風(fēng)格。隨后它啟發(fā)產(chǎn)生了BDD。
2.2.1 主要特點(diǎn)
- 不同于經(jīng)典派,由外及內(nèi)TDD為我們?nèi)绾沃譁y試驅(qū)動代碼做出了規(guī)定:從外(接到外部輸入的第一個(gè)類)到內(nèi)(各個(gè)將會實(shí)現(xiàn)系統(tǒng)需要的一個(gè)單一特性的類)。
- 一般從一個(gè)驗(yàn)收測試開始,驗(yàn)收測試用來檢驗(yàn)是否整個(gè)功能工作。驗(yàn)收測試也為實(shí)現(xiàn)進(jìn)行了向?qū)А?/p>
-驗(yàn)收測試的失敗會告知我們功能還有某些部分沒有實(shí)現(xiàn)的信息(數(shù)據(jù)沒有返回,消息沒有送入隊(duì)列,數(shù)據(jù)沒有存人數(shù)據(jù)庫等等),從中我們可以開始進(jìn)行單元測試。第一個(gè)被測的類負(fù)責(zé)接收外部的請求(比如一個(gè)控制器,隊(duì)列監(jiān)聽器,事件接收器,組件入口等)。
- 鑒于我們知道我們不會在一個(gè)類里實(shí)現(xiàn)整個(gè)程序,我們會假定被測試類會需要一些合作類。然后我們在測試?yán)矧?yàn)證被測類與其合作類之間的協(xié)作。
- 通過被測類需要調(diào)用合作類公開方法來完成的各種事,可以識別出合作類。合作類的名字和方法名應(yīng)該來自于行業(yè)語言中的名詞和動詞。
- 當(dāng)一個(gè)類被完全測試后,我們選取一個(gè)合作類(此時(shí)應(yīng)該還沒有進(jìn)行實(shí)現(xiàn)),并采用與上一個(gè)類相同的方式,通過測試驅(qū)動它的行為。這就是我們叫它由外而內(nèi)的原因:我們從靠近系統(tǒng)輸入的地方(外部)通過不斷識別合作類,逐步向程序內(nèi)部推進(jìn)。
- 設(shè)計(jì)起始于紅燈階段,也就是開始寫測試時(shí)。
- 測試測的是行為和協(xié)作,而非狀態(tài)。
- 在重構(gòu)階段對設(shè)計(jì)進(jìn)行完善。
- 每個(gè)合作類以及它的公開方法都是為了給已有的客戶類提供服務(wù)的,這使得代碼可讀性很高。
- 相比經(jīng)典式方法,由外而內(nèi)式的重構(gòu)階段要小得多。
- 提高了封裝性,因?yàn)椴恍枰獌H僅為了測試而暴露狀態(tài)。
- 更符合“tell, don't ask”設(shè)計(jì)方法。
- 更符合面向?qū)ο缶幊痰脑纠砟睿簻y試是關(guān)于對象間發(fā)送的消息,而非檢測對象的狀態(tài)。
- 適用于商業(yè)應(yīng)用,從user story和驗(yàn)收條件中可以獲得名詞和動詞。(作為類名和方法名)。
2.2.2 問題
- 對于初學(xué)者來說很難接受,因?yàn)樾枰邔哟蔚脑O(shè)計(jì)能力。
- 開發(fā)者從代碼中無法得到反饋來創(chuàng)建合作類。他們需要通過寫測試來使合作類顯現(xiàn)出來。
- 不成熟的創(chuàng)建合作類有可能導(dǎo)致過度設(shè)計(jì)。
- 不適用于探索式的工作,以及不特定于user story的行為(數(shù)據(jù)轉(zhuǎn)換,算法等)。
- 設(shè)計(jì)能力糟糕的話可能會導(dǎo)致mock對象爆炸式的激增。
- 基于行為的測試要比基于狀態(tài)的測試難寫。
- 在寫測試的時(shí)候需要具備DDD(領(lǐng)域驅(qū)動設(shè)計(jì))以及其他設(shè)計(jì)技能,包括簡單設(shè)計(jì)四規(guī)則。
2.3 小結(jié)
雖然兩者各有優(yōu)缺點(diǎn),考慮維護(hù)性和效益問題,一般考慮從外而內(nèi),選擇接口穩(wěn)定,并且具有業(yè)務(wù)含義的用力進(jìn)行測試。
3、落地TDD遇到的問題
推廣TDD,一般阻力都比較大,主要有以下方面:
3.1 質(zhì)量意識
傳統(tǒng)的軟件研發(fā)模式,對于軟件質(zhì)量,常常偏重于軟件的外部質(zhì)量。檢查開發(fā)人員的的工作效益或者質(zhì)量,主要是測試人員發(fā)現(xiàn)的缺陷數(shù)。這種模式下,形成的慣性思維認(rèn)為開發(fā)人員不適合做測試,主要有測試人員進(jìn)行質(zhì)量保證。
推行TDD,以為不僅僅要關(guān)注外部質(zhì)量,又要關(guān)注內(nèi)部質(zhì)量。當(dāng)有開發(fā)人員進(jìn)行TDD時(shí),因?yàn)榇a的可測試性問題,會導(dǎo)致單元測試舉步為艱,這也成為阻力。如果代碼的內(nèi)部質(zhì)量低下,但在足夠覆蓋率的保護(hù)下,使得重構(gòu)變得更為簡單。
?3.2 業(yè)務(wù)分析和拆解能力
在TDD時(shí),如果按照標(biāo)準(zhǔn)的敏捷模式運(yùn)行,需要有完整的故事和AC。如果BA角色缺失或者PO對于AC描述不清,在編寫TDD的用例(To-do list)時(shí),將成為問題。業(yè)務(wù)的分解需要分為三個(gè)層次,即業(yè)務(wù)價(jià)值—>業(yè)務(wù)功能—>業(yè)務(wù)實(shí)現(xiàn)。
?3.3 測試先行的開發(fā)習(xí)慣
養(yǎng)成一個(gè)習(xí)慣需要二十一天,改變一個(gè)習(xí)慣需要六十天。 要讓開發(fā)人員改變習(xí)慣需要一個(gè)過程。記得自己改變的過程中,強(qiáng)制自己通過截屏方式記錄每個(gè)過程。一周下來,基本可以做到測試先行和三步提交原則。
3.4 Clean Code和重構(gòu)
TDD的核心是紅——綠——藍(lán)。藍(lán)色意味著重構(gòu)。重構(gòu)是TDD非常重要的一環(huán),它直接關(guān)系到TDD開發(fā)出來的代碼質(zhì)量。沒有好的重構(gòu)能力,TDD就會有缺失。實(shí)施TDD,需要開發(fā)人員具備Clean Code和重構(gòu)的能力。
3.5 業(yè)務(wù)測試框架
良好的測試框架,使用測試用例編寫和測試數(shù)據(jù)管理變得容易。在測試框架的付出是需要值得。自己的團(tuán)隊(duì)在推廣TDD,也是得意于良好的測試用例,從而保證4個(gè)月時(shí)間,測試用例從200增長到1000以上,覆蓋率從20%達(dá)到39%以上。