官术网_书友最值得收藏!

前言

測試驅動開發(fā)是一種在編程時克服恐懼的方式。

——Kent Beck

你知道我們有多幸福嗎?TDD已經伴隨我們好多年了。

幾十年之前,水星計劃(Project Mercury)的開發(fā)者就已經開始在穿孔卡片上實踐TDD了(參見:https://oreil.ly/pKpSZ)。世紀之交,xUnit庫讓更多的人開始采用TDD做開發(fā)。Test-Driven Development: By Example(《測試驅動開發(fā)》,Addison-Wesley Professional, 2002)一書的作者Kent Beck還開發(fā)過JUnit測試框架,他說自己并沒有發(fā)明(invent)測試驅動開發(fā),只不過是“重新發(fā)現(xiàn)”(rediscovered)了它而已(參見https://oreil.ly/zDyBr)。這當然是一種謙虛的說法,但也道出了事實——TDD的歷史其實跟軟件開發(fā)一樣長。

既然如此,那為什么TDD沒有成為編寫代碼的標準方式呢?在日程緊迫或者IT方面的預算必須削減的時候,在我們(這其實是筆者自己)想要“提升軟件交付團隊的速度”的時候,為什么TDD是首先被砍掉的方式呢?根據(jù)經驗與實驗數(shù)據(jù)(參見https://oreil.ly/2Xxyb),TDD能夠降低缺陷數(shù)量、簡化設計方案,而且能夠讓開發(fā)者對自己所編寫的代碼更有信心。既然有這么多立竿見影的好處,那為什么我們還會以各種理由棄用TDD呢?

許多人不愿意使用TDD,即便用了也會很快就放棄。為什么會這樣呢?這些人給出的理由包括下面幾條:

我不知道從哪里開始做TDD,也不知道怎樣做TDD

最常見的原因恐怕就是對TDD缺乏認識,不愿意接觸TDD。其實與其他技能類似,用測試驅動的方式編寫代碼也是需要學習的。許多開發(fā)者之所以不愿意學習TDD可能是因為缺乏外部誘因(例如缺少時間、資源、指導、勇氣),也可能是因為缺乏內在動機(例如缺少克服不情愿心理或恐懼心理的動力)。

TDD只能用在那種為了教學而設計的小程序里面,或是為了招聘而舉行的編程面試之中,它不適合編寫現(xiàn)實工作中的代碼。

這種說法不對,但是筆者可以理解為什么有人會這么說。大多數(shù)講解測試驅動開發(fā)的教程(當然也包括本書)都會從大眾熟知的領域里面選擇相對簡單的例子。想從一款部署在商用場合的應用程序(例如那種部署在金融機構、醫(yī)療管理系統(tǒng)或無人駕駛汽車里面的應用程序)中選擇一段實際的代碼,并根據(jù)這樣的代碼撰寫文章或書籍是相當困難的。其中一個原因在于,現(xiàn)實工作中的代碼大多數(shù)是私有的,并不開源。另一個原因則在于,文章或書籍的作者總是想選擇一個受眾最為廣泛的領域來講解。作者沒有理由選用某個極為專業(yè)的領域來演示TDD,因為那樣是不明智的。假如非要那樣做,那么在開始之前,必須花很長的時間去解釋那個領域的一些晦澀術語及行話。不過這樣產生的效果與作者想要的效果相反,作者本來是想讓大家理解、接受乃至喜愛TDD這種開發(fā)方式的。

雖然編寫TDD教材時不便采用現(xiàn)實工作中的代碼來舉例,但是開發(fā)者在編寫日常工作中的軟件時,確實能夠得益于測試驅動開發(fā)這一方法。最典型也最有力的一個例子可能就是JUnit框架自己的單元測試套件了(參見https://oreil.ly/UCPcg)。另外,Linux Kernel(Linux內核)應該算是世界上使用頻度最高的軟件了吧?它的代碼也正在通過單元測試得以改進(參見https://oreil.ly/hBbq0)。

只要能在產品代碼寫出來之后補寫測試代碼就行了,要求開發(fā)者率先編寫測試代碼顯得太過嚴苛或者迂腐。

這條理由要比“單元測試被高估了”(參見https://oreil.ly/Y7S5M)之類的說法聽起來新鮮一些。寫好產品代碼之后再編寫測試確實要比根本不寫測試強。凡是能讓開發(fā)者對他們所寫的代碼更有信心、減少意外的復雜性和促使他們寫出確切文檔的做法都值得提倡。然而,在編寫產品代碼之前率先編寫單元測試則能夠讓開發(fā)者根本沒有機會向代碼中引入復雜性。

TDD以下面這兩條務實的原則為基礎指引我們做出更為簡單的設計:

1.編寫產品代碼只是為了讓無法通過的測試得以通過。

2.只在測試能夠通過的前提下才去奮力地重構代碼。

那么,這是否意味著TDD必定能讓我們寫出最為簡單且符合需求的代碼呢?其實并不一定。沒有哪種做法、哪條規(guī)則、哪本書籍或哪個宣言能夠做到這一點。能否運用TDD寫出簡單而有效的代碼,其實得看開發(fā)者究竟是如何運用TDD的。

本書想要用三種編程語言來解釋什么是測試驅動開發(fā),并告訴大家怎樣做測試驅動開發(fā)。筆者的目標是讓開發(fā)者在日常工作中養(yǎng)成用TDD來編寫代碼的習慣,并堅持這一習慣。這個目標或許有點大,但我還是希望自己能夠在某種程度上實現(xiàn)它。

什么是測試驅動開發(fā)

測試驅動開發(fā)是一種用來設計代碼并調整其結構的技術,促使開發(fā)者在代碼規(guī)模不斷增加的過程中依然能夠寫出簡潔的代碼并對其所寫的代碼更有信心。

下面我們就看看這個定義所涉及的幾個方面。

TDD是一種技術

TDD是一種技術。這種技術當然也蘊含著一套與代碼有關的理念,這就是:

? 以簡潔為本——簡潔是盡量避開無謂之事的藝術[1]2

? 直白而清晰要比精巧更為重要。

? 編寫整潔而不雜亂的代碼是取得成功的關鍵因素。

雖說TDD源自這些理念,但它依然是一種實用的技術。它跟騎自行車、揉面或解微分方程一樣,并不是每個人天生就會的,而是必須靠學習才能掌握。

本書不打算在其他地方重復講述TDD背后的這套理念。筆者假定你已經同意該理念,或者愿意嘗試TDD這種技術。

本書中大部分內容都依照這樣三個環(huán)節(jié)來運用TDD:首先編寫一個失敗的單元測試;其次編寫剛剛夠用的生產代碼,讓該測試得以通過;最后花時間清理代碼。大家將有相當多的機會來親自嘗試TDD這種技術。

總之,比較理想的結果是,你不僅學會了這種技術,而且還培養(yǎng)出讓自己堅持使用該技術的信念。這就好比你不僅學會了騎自行車,而且還能經常提醒自己這是一種既鍛煉身體又不污染環(huán)境的出行方式。

TDD是一種用來設計代碼并調整其結構的技術

請注意,TDD并不是為了測試代碼而測試代碼。雖然我們會用單元測試驅使自己編寫程序,但TDD的最終目標在于通過測試設計出更好的、結構更佳的代碼。

這一點是很重要的。假如TDD單純是為了測試而測試,那就無法有力地論述為何必須在編寫業(yè)務代碼之前先編寫測試,如果是只要有測試代碼就行,那么寫完業(yè)務代碼之后再寫測試,不也一樣嗎?測試先行,是為了激勵我們設計出更好的軟件,因此,測試僅僅是促進該過程順利執(zhí)行的一種手段。TDD最后確實會形成一套單元測試,但這只不過是錦上添花,我們的主要成果在于有了一套簡潔的設計方案。

那么,究竟怎樣才能得到簡潔的設計方案呢?這要通過紅-綠-重構這三個環(huán)節(jié)來實現(xiàn),具體細節(jié)會在第1章開頭講解。

TDD是崇尚簡潔的

我們不能僅僅認為簡潔是個虛幻的概念,因為在軟件開發(fā)領域,它其實是可以度量出來的。比方說,我們可以從實現(xiàn)每個功能所需的平均代碼行數(shù)、循環(huán)復雜度(參見https://oreil.ly/5Gj2b)、副作用、運行時庫的大小以及內存占用量等指標里面選出一個或多個(也可另行采用其他指標)來度量簡潔程度,這些指標越低,簡潔程度就越高。

測試驅動開發(fā)逼著我們“用最簡單的辦法把事情做成”(也就是說,只寫出剛剛能讓所有測試得以通過的代碼就好),這樣的話,我們就總是能夠在衡量簡潔程度的那些指標上面取得高分。我們不會再因為“萬一要用到”或“馬上就得寫”之類的理由去編寫多余的代碼。由于有了一個失敗的測試擺在我們面前,因此我們只需寫出能讓該測試以及早前已經通過的那些測試得以通過的代碼即可。測試先行是一種動力,促使我們把復雜的問題及早暴露出來。如果我們要開發(fā)的功能定義得不夠明確,或者我們對該功能的理解有所偏差,那么很難寫出良好的測試,這會促使我們在還沒有開始編寫任何一行生產代碼之前,就先把這些與功能有關的問題給搞清楚。這正是TDD的真諦:如果能夠通過測試來推進編程工作,那就可以把遇到的每一個復雜問題都清理掉。

當然,TDD的好處也并沒有那么神奇,你不要想著一旦開始做測試驅動開發(fā),你的編程時間、代碼行數(shù)與缺陷數(shù)量就會立刻減半。它的好處在于讓你打消多余的念頭,別再根據(jù)自己虛構或臆想出來的需求去編寫復雜的代碼。由于你先寫了一個無法通過的測試,因此你會用最直白的代碼讓該測試得以通過,也就是說,你會寫出最簡單,并且能夠滿足該測試所提需求的代碼。

TDD能讓開發(fā)者對代碼更有信心

代碼應該能讓我們對項目更有信心才對,如果這些代碼是我們自己寫的,那么更應該是這樣。這種信心或許不太好描述,但總之,它源自一種對預測能力的欣賞。如果某個東西或某件事情的行為是可以預測的,那我們就會對其比較有信心。比方說,如果街角的那家咖啡店,今天少收我?guī)自X,明天又多收我?guī)自X,那么就算我連續(xù)兩天去那里消費,也還是有可能對店員的工作水平失去信心。如果能夠看清某件事情的規(guī)律并預測其走向,那我們會覺得這比單純擁有該物更有價值,人性就是如此。全世界運氣最好的賭徒,哪怕在輪盤賭上面連贏十次,也不會說自己對這個輪盤“信任”或“有信心”吧?比起好運,我們總是更看重預測能力或者穩(wěn)定發(fā)揮的能力。

測試驅動開發(fā)讓我們對代碼更有信心,因為每寫一個新的測試,系統(tǒng)就會朝著新的方向邁進一步,讓我們能夠看到系統(tǒng)在這個新的方向之下表現(xiàn)得如何,這一點確實是我們在編寫這個新的測試之前不知道的。這樣的一套測試能夠幫助我們避開回歸故障(regression failure)[2]

由于有了一套越來越扎實的測試,因此我們能夠更加確信代碼的質量也會隨著規(guī)模的擴大逐漸提升。

目標讀者

本書是給開發(fā)者看的,或者說,是給編寫軟件的人看的。

這個職業(yè)有許多種叫法,例如“軟件工程師”(software engineer)、“應用程序架構師”(application architect)、“運維工程師”(devops engineer)、“測試自動化工程師”(test automation engineer)、“程序員”(programmer)、“黑客”(hacker)、“說碼語的人/碼語者”(code whisperer)等。無論這些職銜是張揚還是謙遜,是活潑還是嚴肅,是傳統(tǒng)還是前衛(wèi),擁有該職銜的人都具備這樣一個特點:每天或者至少每周有一部分時間要坐在計算機前閱讀或編寫代碼。

我用開發(fā)者(developer)這個詞來稱呼這群人,因為我本身就是個平凡的開發(fā)者,同時也覺得自己很榮幸有這個機會來做開發(fā)。

編寫代碼是我們可以想象的一種極為自由而平等的活動。從理論上來說,唯一的身體要求就是要有頭腦。除此之外,年齡、性別、國籍與族裔等因素通通不是障礙。就算身體行動不便,也不妨礙編寫代碼。

然而,現(xiàn)實卻不是這樣簡單而公平,因為并非每個人都能得到計算機資源。此外,單憑興趣與努力,未必能夠把編程堅持學下來,因為你可能會受到各種各樣的打擊,例如學習編程時所參照的那些軟件寫得很爛,所使用的那些硬件設計得很糟,或是由于其他許多因素而無法順暢地學習。

筆者盡量讓本書易于閱讀,尤其是讓身障人士能夠方便地學習。因此,書中的圖片都配有替代文字,以便讓電子閱讀器可以讀出這些文字。書里的代碼都可以通過GitHub獲取,而且文風也很直白。

本書對讀者的編程經驗沒有過高的要求,還沒有徹底學會編程的人以及已經學會了編程的人,都可以閱讀本書。如果你正在提升自己對筆者所選的某一種(或某幾種)編程語言的熟悉程度,那當然更應該來讀本書了。

然而,本書并不會講解任何一門編程語言的基礎知識,其中也包括本書所要使用的Go、JavaScript及Python語言。因此,讀者必須能夠讀懂并且會使用至少一種編程語言。如果你完全是編程新手,那最好是先從這三門語言里面選一門,把該語言的編程基礎鞏固好,然后再讀本書。

本書的受眾涵蓋各種水平的開發(fā)者——從剛學編程的人到很有經驗的架構師。圖P-1描繪了本書的讀者范圍(Kent Beck不在此列)。

圖P-1:這本書適合各種水平的軟件開發(fā)者閱讀

編寫代碼可能是一件讓人時喜時怒的事情。但即便在最為喪氣的時候,你也總應該能找到一股樂觀情緒與自信心理,覺得自己這次肯定會把代碼寫好。只要堅持下去,你就會發(fā)現(xiàn)閱讀本書很有收獲,而且會覺得用測試驅動的方式編寫代碼很有意思,希望你在讀完本書之后能夠繼續(xù)采用這種方式編程。

閱讀前準備

在設備與技術要求方面,你需要做到這樣幾條:

? 有一臺能夠連接互聯(lián)網(wǎng)的計算機。

? 有權安裝并刪除該計算機之中的軟件。也就是說,你對計算機的使用權不受限制,在大多數(shù)情況下,這意味著你需要成為該計算機的“Administrator”(管理員)或“Superuser”(超級用戶)。

? 能夠啟動并使用shell環(huán)境(也就是命令行環(huán)境)、網(wǎng)頁瀏覽器與文本編輯器,最好還能安裝一套IDE(集成開發(fā)系統(tǒng))。

? 已經(或者能夠)安裝本書所采用的三門語言之一所需要的運行時工具。

? 能夠用本書所采用的三門語言之一來編寫“Hello World”程序并運行該程序。

如何安裝這些工具,詳見0.1節(jié)。

如何閱讀本書

本書的主題是“如何用Go、JavaScript與Python做測試驅動開發(fā)”,筆者所講的概念全都適用于這三種語言,然而針對每一種具體的語言,還是會分開講解。與學習其他技能類似,要想學習測試驅動開發(fā),最好的辦法就是在實踐之中學習,也就是邊練邊學。筆者建議大家不僅要閱讀書中的文字,而且要自己編寫代碼。我把這種學習方式叫作“跟著書走”(follow the book),它會讓你每讀一段就去學著編寫代碼,把剛讀過的這段內容演練一遍。

要想最充分地利用本書,你應該分別用三種語言來編寫這個名為Money的范例程序。

本書許多章都包含這樣的一節(jié),它適用于全部三種語言,該節(jié)包含三小節(jié),分別描述每一種語言的代碼應該如何編寫。這三小節(jié)的標題與該小節(jié)所針對的語言是一致的,也就是說,講Go語言的那一小節(jié)就叫作Go,講JavaScript語言的那一小節(jié)就叫作JavaScript,講Python語言的那一小節(jié)就叫作Python。每章最后會用一或兩節(jié)來概括目前已經取得了哪些成績,以及接下來應該做些什么。

但是,第5~7章比較特殊,因為這三章是每章專講一種語言,而不是把三種語言全都放到該章的某一節(jié)里面講解。

圖P-2演示了本書的布局,以及用Go、JavaScript與Python語言來學習本書內容時所遵循的路線。

圖P-2:閱讀本書的流程

下面是閱讀本書的幾種推薦方式。

一次使用一種語言學習本書

如果你符合下面的一個或多個條件,那么筆者建議你采用種方式學習本書:

1.在使用其他兩種語言之前,我特別想要先采用某種語言來編程。

2.我很好奇(或者很懷疑)如何使用這三種語言中的某一種語言做TDD。

3.我擅長一次只用一種語言學習,而不擅長同時采用多種語言學習。

你每次可以沿著圖P-2中的一條線往下閱讀。比方說,如果你很想先搞清楚怎樣用Go語言做TDD,那么就把專門針對JavaScript與Python的那些章節(jié)跳過去。第二次閱讀本書時換一種語言來學習,比如換用JavaScript語言。到了第三次,再換用一種語言,比如Python。當然你也可以改用另一套順序把這本書讀三遍。第二次與第三次讀,應該比第一次快,但是要注意,每種語言可能都有一些特殊的地方。

如果以這種方式閱讀本書,那么你會依次采用三種語言做TDD,這能夠讓你更真切地體會到TDD其實是一個原則,而不單單是對某一門語言的測試功能所做的運用。培養(yǎng)編寫測試的習慣固然很重要,然而弄清楚測試驅動開發(fā)為什么在各種語言中都行得通是一件更為重要的事。

先同時使用兩種語言學習一遍,然后再使用另一種語言學習

如果你符合下面所說的任意一個條件,那么筆者建議你采用這種方式學習本書的內容:

1.我想要用兩種語言解決同一個問題,并在這兩種解法(解決方案)之間對比。

2.我不太喜歡這三種語言里面的某一種,所以想先用另外兩種語言把這本書學一遍,然后再用這種語言學習。

3.我可以同時采用兩種語言學習,但要是三種就有些難了。

你可以同時沿著圖P-2中的兩條線把本書學習一遍。等到你用這兩種語言將Money范例程序寫好之后,再采用另外的語言來學習第二遍。

如果你已經決定先使用其中的兩種語言,但還不知道應該選哪兩種(或者說,還不知道應該把哪一種語言推遲到第二輪),那么可以根據(jù)下面這些建議來考慮:

1.你是不是想在動態(tài)類型的語言與靜態(tài)類型的語言之間對比,而且不想使用技術棧太過繁雜的語言?如果是這樣,那就先用Go與Python學習本書,然后再用JavaScript學習。

2.你是不是想用兩種區(qū)別很大的方式來編寫代碼,而且愿意面對各種各樣的技術棧?如果是這樣,那就先用Go與JavaScript學習本書,然后再用Python學習。

3.你是不是想先在兩種動態(tài)語言之間對比?如果是這樣,那就先用JavaScript與Python學習,然后再用Go。

以這種方式學習本書,你很快就能看到用各種語言做TDD時的相同與不同之處。這些語言在語法與設計上的差別可能會讓你采用明顯不同的方式做TDD,但你同時可能也會感覺到TDD這種開發(fā)方式對代碼的寫法影響真的很大,無論具體使用哪種語言編寫代碼,做TDD跟不做TDD時的寫法都完全不同。

同時使用三種語言學習本書

如果你屬于下面三種情況之一,那么建議你采用這樣的方式學習:

1.你覺得同時用這三種語言學習可以更好地了解它們之間的差異與相似之處。

2.你覺得把一本書從頭到尾完整地讀一遍要比沿著不同的路徑讀好幾遍更為容易。

3.你在這三種語言上已經有了一些經驗,但還沒試過用其中任何一種語言做TDD。

如果你能同時用三種語言編寫代碼而不覺得眼花,那么建議你這樣來學習本書。

無論用哪種方式讀這本書,都必須注意寫代碼的時候可能會遇到一些與你的開發(fā)環(huán)境有關的具體困難。這本書的代碼已經測試無誤(而且它的持續(xù)集成也做得沒有問題,參見https://github.com/saleem/tdd-book-code/actions),但這并不意味著這些代碼在你的計算機上一次就能運行成功。(相反,幾乎可以斷定,你總是得費點功夫去解決一些很有意思的問題。)TDD的一個關鍵優(yōu)勢在于開發(fā)速度能夠由你自己掌控。如果某一部分推進得比較艱難,那就把速度放慢。每次只前進一小步,這樣更容易發(fā)現(xiàn)代碼是在你推進到哪一步時出現(xiàn)錯誤的。編寫軟件的過程中,總是需要處理各種錯亂的依賴關系,還要應對不夠穩(wěn)定的網(wǎng)絡連接與蹩腳的工具,并忍受代碼中的種種毛病。如果你覺得應付不過來,那就每次只處理一個具體的問題,只改動一點點代碼。總之,你要記住,TDD是一種幫助你克服編程恐懼的方式。

本書所采用的格式

本書有兩方面的格式需要解釋,一個是印刷格式,另一個是措辭格式。

印刷格式

有的時候,在敘述之中也會出現(xiàn)代碼中的一些詞,例如class、interface或Exception等,這種詞采用等寬字體印刷。采用這種字體是為了提醒你這些詞在代碼里面也是這樣寫的。

大段的代碼會分成多個代碼塊,例如:

? 省略號表示與當前講解的內容無關的代碼,或被略去的一些輸出信息。

代碼塊里的內容,要么是需要照原樣輸入的代碼,要么就是程序所輸出的一些信息。但是有兩個地方例外。

1.代碼里的省略號(...)表示這里略去了一些代碼或輸出文字,這些代碼或文字與當前所要講解的話題無關。不要把這個省略號也錄入代碼里,也不要認為它會出現(xiàn)在輸出信息中。剛才那段代碼就出現(xiàn)了省略號。

2.如果代碼塊表示的是輸出信息,那么其中可能會出現(xiàn)一些臨時的值(ephemeral value),例如內存地址、時間戳、持續(xù)時長、行號、自動生成的文件名等,你在自己的計算機上看到的這些值不太可能跟書里印的值一樣。在閱讀這種輸出信息時,你可以略過這些具體的值,比方說,下面這個代碼塊里的內存地址,其具體數(shù)值就不重要:

表示技巧,這是一些對你編寫代碼有所幫助的建議。它們與正文分開書寫,便于參考。

表示與主題有關的重要信息。這些信息通常是指向某些資源的超鏈接或附注,這樣的資源會為該主題提供更多內容。

本書有許多章都會分別采用Go、JavaScript與Python這三種語言來深入講解開發(fā)技術并討論相關的代碼。只有第5~7章例外,它們都只針對一門語言而寫。為了把討論每一種語言所用的那些文字隔開,本書會用一個標題來指明這種語言,并在頁邊添加一個與該語言相對應的圖標,以提醒你接下來的內容是專門針對這種語言而寫的。請記住下面這三種標題以及與之相配的圖標:

? Go

? JavaScript

? Python

措辭格式

本書要討論一些核心的軟件開發(fā)概念,并用三種語言編寫代碼,以提升討論效果。由于這三種語言各自的術語區(qū)別很大,因此想用同一套說法概括這樣三個系列的術語是相當困難的。

比方說,Go語言沒有類,也沒有基于類的繼承機制。而JavaScript語言的類型體系里則有基于原型的對象,這意味著它的所有東西其實都是對象,也包括我們一般認為應該是類的那種東西。Python支持基于類的對象,本書會用一種“比較傳統(tǒng)的”手法來使用Python的對象[3]。探討Go語言的時候,別說“我們要創(chuàng)建一個名叫Money的新類”,這種說法不僅糊涂,而且完全錯誤。

為了減少誤會,筆者決定采用表P-1中這套比較通用的說法來指代各語言里的關鍵概念。

表P-1:本書所采用的一套通用術語

筆者之所以選用自己的這套術語,是因為不想特意使用其中某一種編程語言的那套說法來講解本書所包含的概念。閱讀本書最大的收獲應該是讓你意識到,任何一種編程語言都可以做測試驅動開發(fā)。

然而,本書在專門講到三種語言里的某一種時,則會使用該語言本身的說法(這樣的文字都出現(xiàn)在與該語言相應的標題之下)。比方說,在專門針對Go的那一部分里面,可能會出現(xiàn)“定義一個名為Money的新結構體”這樣的話。上下文會使讀者很清楚這條指令是針對特定語言的。

示例代碼

可以從https://github.com/saleem/tdd-book-code下載示例代碼。

這里的代碼是為了幫助你更好地理解本書的內容。通常,可以在程序或文檔中使用本書中的代碼,而不需要聯(lián)系O'Reilly獲得許可,除非需要大段地復制代碼。例如,使用本書中所提供的幾個代碼片段來編寫一個程序不需要得到我們的許可,但銷售或發(fā)布本書中的示例代碼則需要獲得許可。引用本書的示例代碼來回答問題也不需要許可,將本書中的很大一部分示例代碼放到自己的產品文檔中則需要獲得許可。

非常歡迎讀者使用本書中的代碼,希望(但不強制)注明出處。注明出處時包含書名、作者、出版社和ISBN,例如:

Learning Test-DrivenDevelopment,作者Saleem Siddiqui,由O'Reilly出版,書號978-1-098-10647-8。

如果讀者覺得對示例代碼的使用超出了上面所給出的許可范圍,歡迎通過permissions@oreilly.com聯(lián)系我們。

如何聯(lián)系我們

要詢問技術問題或對本書提出建議,請發(fā)送電子郵件至errata@oreilly.com.cn

本書配套網(wǎng)站https://oreil.ly/learningTDDbook上列出了勘誤表、示例以及其他信息。

關于書籍、課程、會議和新聞的更多信息,請訪問我們的網(wǎng)站http://oreilly.com

與TDD有關的幾個“為什么”

對TDD(這里主要指對本書)的批評會以多種形式發(fā)表出來。其中有些形式確實很搞笑,比方說圖P-3中的漫畫。這幾幅清新的幽默圖畫是Jim Kersey繪制的。

圖P-3:TDD笑話一則——橋都沒建好,讓我怎么走(來源:https://robotkersey.com/

說認真的:大家對書的內容和結構有意見,其實很正常。下面就來回答幾個這方面的問題。

為什么要使用Go、JavaScript與Python這三種語言

本書使用Go、JavaScript與Python這三種語言演示測試驅動開發(fā)。有人會問:為什么選這三種語言呢?

下面解釋原因。

1.這三種語言能涵蓋多種設計方案

如表P-2所示,這三種語言在各方面擁有不同的特性,能夠涵蓋很大一批設計方案。

表P-2:對比Go、JavaScript及Python語言

表P-2:對比Go、JavaScript及Python語言(續(xù))

2.這三種語言比較流行

Python、JavaScript與Go是開發(fā)者最想學習的三種新語言,這可以從Stack Overflow網(wǎng)站在2017(https://oreil.ly/CbnCx)、2018(https://oreil.ly/uhhLx)、2019(https://oreil.ly/BdAQJ)與2020年(https://oreil.ly/mHqNs)的年度調查里看出來。圖P-4是2020年的調查結果。

圖P-4:開發(fā)者最想學習的新語言,數(shù)據(jù)來自Stack Overflow的調查報告

在2021年的Stack Overflow調查數(shù)據(jù)(參見https://oreil.ly/hzMVk)中,TypeScript爬到了第二位,把JavaScript與Go分別擠到了第三與第四位,Python依然處在榜首。

從語法上講,TypeScript是JavaScript的超集(參見https://oreil.ly/aATAD)。因此可以說,每一個想要學習TypeScript語言的開發(fā)者其實都必須了解JavaScript。我希望使用TypeScript語言的開發(fā)者也認為本書的內容有價值。

3.個人原因

過去幾年,筆者有機會參與許多項目,那些項目的技術棧都包含這三種語言之一。在跟其他開發(fā)者合作的過程中,我發(fā)現(xiàn)有許多人雖然想學習并實踐TDD,但卻不會尋找做TDD所需的資源(或者無法堅守做TDD的紀律)。他們打算實踐TDD,然而不知道應該怎樣做,或者找不到合適的時間來做。這種現(xiàn)象在有經驗的開發(fā)者與“菜鳥”(noob)身上都會出現(xiàn)。

筆者希望,想用任何一種語言來學習并實踐TDD的開發(fā)者都能把本書當成實踐指南并從中獲得靈感,而不希望把本書僅僅局限在Go、JavaScript與Python這三種語言的范圍內。

為什么不采用其他語言來講解

對初學者來說,有大量的編程語言可供考慮。所以,就算我們分別采用好幾種語言來寫一系列的教程,也只能涵蓋這些編程語言之中的一小部分,因為開發(fā)者在日常工作中為了學術、商務及娛樂等目標而編寫代碼時,所采用的語言實在是太多了。

另外,Java語言已經有一本很棒的測試驅動開發(fā)教程了[4]。與其他許多開發(fā)者一樣,筆者本人也從Kent Beck的那本杰作之中獲得了許多啟發(fā),它讓我迷上了TDD這門技藝。那本書是用一個關于錢的問題來舉例的,這促使我在本書中也舉了這樣的例子。

我當然知道,針對其他許多種編程語言來寫TDD教程也很有幫助。例如可以針對R語言、SQL語言,乃至COBOL語言來制作TDD指南。

這里提到COBOL語言并不是信口胡說。在2005年左右,筆者參與過一個項目,當時我演示了如何通過COBOLUnit在COBOL語言里做TDD。這是我用一種比我還要大十幾歲的老式語言學到的最有趣的東西。

所以,筆者希望你能接過這個重任。你應該自己去學習如何用其他語言做測試驅動開發(fā),把學到的經驗教給大家,鼓勵大家堅持使用TDD寫程序,并遵守做TDD時的紀律和原則。你可以寫博客,做開源項目,或者再寫一本這方面的書。

為什么要有第0章

大多數(shù)編程語言都使用從0開始的計數(shù)方式來標識數(shù)組或其他可數(shù)序列之中的元素[5],本書所采用的這三種編程語言當然也是這樣。從0開始計數(shù)已經是一種頗有歷史的編程文化了,因此,章的編號從0開始算,可以說是在尊重這個悠久的傳統(tǒng)。

另外,我還要向0這個數(shù)字本身致敬,有人可能覺得這個想法很瘋狂。Charles Seife專門寫過一本書來談這個數(shù)。在追溯0的歷史時,Seife指出,希臘人有一個用來表示“無”的數(shù):

在那個【也就是希臘人的】思想世界里面,沒有“無”這個東西。那里沒有零。因此,西方在將近兩千年的時間里都不接受零這個概念。這導致了很嚴重的后果。缺乏零的概念阻礙了數(shù)學發(fā)展,遏制了科學創(chuàng)新,而且連日歷紀年也跟著遭殃。西方哲學家必須先打破他們固有的思想,然后才有可能接受零這個概念。

——Charles Seife,Zero: The Biography of a Dangerous Idea(《神奇的數(shù)字零》)

雖然這樣說可能有點夸張,但測試驅動開發(fā)在編程界的地位確實與零這個概念在幾千年前的西方哲學中的地位有些相似。總是有人不愿意接受它,這可能是因為他們一方面輕視這個概念,另一方面又覺得它很別扭,而且認為在什么產品代碼都沒有的情況下寫測試實在是太麻煩了。“我為什么非得先寫測試呢?我難道不清楚自己準備實現(xiàn)什么功能嗎?”“測試驅動開發(fā)太迂腐了,這只是個理論而已,很難實踐。”“把產品代碼寫完之后再測試,其實比先寫測試更管用,反正至少不會比它差。”許多人用諸如此類的理由來反對TDD,這與當年那些人拒絕零這個概念何其相似,他們都認為這種東西是荒謬的。

其實書里出現(xiàn)第0章并非完全沒有先例。Carol Schumacher寫過一本書,名字就叫作Chapter Zero: Fundamental Notions of Abstract Mathematics(參見https://oreil.ly/nXJdV),許多大學課程都把它當作一本標準的高等數(shù)學教科書。現(xiàn)在來個無獎競猜:起這樣一個名字的書,會從第幾章開始呢?

在Schumacher博士為這本書寫的教師手冊中有這樣一句話,我覺得很有啟發(fā):

身為作者,你的任務是給讀者提供適當?shù)奶崾荆屗麄兒苋菀拙湍苎@些提示理解你想要表達的意思。

——Carol Schumacher為Chapter Zero這本書所寫的教師手冊

筆者覺得這條建議很有道理。從實際效果看,第0章能夠將該章與其后各章明確區(qū)分開。第1章會帶著我們正式開始TDD之旅,我們要通過它與它后面的十幾章來正式地學習TDD。然而在這之前,我們先要通過第0章了解啟程前需要知道和準備的一些東西,以及這段旅程會帶給我們什么收獲。

把需要解釋的問題說清之后,我們就可以進入第0章了。


[1] 簡潔(simplicity)的定義,參見“Agile Manifesto”(敏捷軟件開發(fā)宣言)十二項原則(https://agilemanifesto.org/principles.html)里面的第10項。(中文版的十二項原則,參見https://agilemanifesto.org/iso/zhcht/principles.htmlhttps://agilemanifesto.org/iso/zhchs/manifesto.html。)

[2]:也叫作回歸失敗,是指那種以前能夠正常運作的功能在修改代碼之后無法正常運作,或是某種較為通用的實現(xiàn)方式在修改之后變得不夠通用的現(xiàn)象。所謂回歸,意思是說以前的bug或以前那種比較差勁的代碼現(xiàn)在又重新回來了。

[3] Python對OOP(面向對象編程)其實支持得相當流暢。比方說,你可以看看prototype.py(參見https://oreil.ly/ZKivt),這是個用Python實現(xiàn)的基于原型的對象系統(tǒng)。

[4]:應指Test Driven Development: By Example(《測試驅動開發(fā)》)。

[5] Lua顯然是個例外。筆者的朋友Kent Spillner對此發(fā)表過一番妙論,我把它總結到了這里:https://oreil.ly/E9M41

主站蜘蛛池模板: 朔州市| 漳平市| 盘山县| 屯昌县| 平阳县| 镇沅| 高要市| 延吉市| 水城县| 九龙城区| 桂东县| 高清| 赣州市| 西畴县| 西盟| 常山县| 齐河县| 拉萨市| 清苑县| 南皮县| 莱州市| 沁阳市| 周口市| 兰溪市| 修水县| 马鞍山市| 武胜县| 定襄县| 昌江| 瓦房店市| 肥城市| 土默特左旗| 嵊泗县| 彩票| 西昌市| 丽水市| 吐鲁番市| 平凉市| 姚安县| 汉寿县| 呼和浩特市|