敏捷——測試先行方法介紹
是的,現在肯定有讀者會這樣說了:“我只在產品發品之前寫測試。”有些人可能會竊笑,對質量保證部門說三道四。還有一些人作為項目經理可能會添油加醋地說:“我們可不會浪費時間寫測試代碼;我們還得寫真正的代碼呢。”那么,采用TDD到底是什么意思呢?
TDD產生于敏捷開發運動,特別是極限編程(extreme programming,XP),而且TDD正是XP的一個核心原則。推崇TDD的人認為,不應該完成開發之后再寫測試,這通常只是“馬后炮”,而應當在寫代碼之前先寫測試。測試本質上相當于設計文檔,而不是花大量時間去擺弄一個復雜的圖形化工具,你要直接在代碼中“擬畫”一個類。開始時先為一些小功能塊編寫測試。在有些語言下,你寫的測試甚至不能編譯,因為所引用的類尚不存在。一旦建立了測試,就可以運行這個測試(當然,此時運行測試會失敗)。然后再編寫最少量的代碼,以便測試通過。接下來再重構代碼,并增加更多的測試。
通常可以使用測試框架來幫助編寫自動化測試。最著名的框架是JUnit,不過現在已經有很多xUnit項目,可以簡化在各種語言下測試的創建。一般地,這些框架都建立在斷言(assert)基礎上。開發人員編寫測試方法,將調用方法的實際結果與期望結果進行比較。當然,可以人工地檢查一個日志文件或用戶界面來完成這些比較,但是,用計算機完成數據比較不僅速度快,也更準確。另外,就算是讓計算機把同樣的測試運行上1500次,它們也不會嫌煩,如果是人可做不到這一點。
有了JUnit和其他測試框架,編寫和運行測試變得相當簡單。這就能鼓勵開發人員創建大量測試(往往能更完備地覆蓋各種測試情況),而且會樂于經常運行這些測試(可以更快地幫助開發人員找到bug)。在許多情況下,如果項目中采用了TDD,測試代碼往往與生產代碼一樣多!
使用TDD可以帶來許多重要的好處:
提供明確的目標:你很清楚,一旦結束(也就是一旦測試通過),你的工作就完成了(假設你的測試寫得很好)。測試會為代碼建立一個自然的邊界,使你把重點集中在當前任務上。一旦測試通過,就有確切的證據證明你的代碼能工作。相對于人工地測試用戶界面或者比較日志文件中的結果,在一個xUnit框架中運行自動化測試,速度要快幾個數量級。大多數xUnit測試的運行只需幾微秒,而且大多數采用TDD的人都會一天運行數次測試。在許多開發小組中,將代碼簽入源代碼樹之前,代碼必須成功地通過測試。
提供文檔:你是不是經常遇到看不懂的代碼?這些代碼可能沒有任何文檔說明,而且開發代碼的人可能早就走了(或者度假去了)。當然,看到這種代碼的時間往往也很不合時宜,可能是凌晨3點,也可能有位副總在你旁邊大聲催促著趕快解決昨天的問題,這樣要想花些時間來明白原作者的意圖就更困難了。我們見過一些好的單元測試文檔,它們會指出系統要做什么。測試就像原開發人員留下的記號,可以展示他們的類具體是怎么工作的。
改善設計:編寫測試能改善設計。測試有助于你從界面的角度思考,測試框架也是代碼的客戶。測試能讓你考慮得更簡單。如果你確實遵循了“盡量簡單而且行之有效”的原則,就不會寫出篇幅達幾頁的復雜算法。要測試的代碼通常依賴性更低,而且相互之間沒有緊密的聯系,因為這樣測試起來更容易。當然,還有一個額外的作用,修改起來也會更容易!
鼓勵重構:利用一套健壯的測試集,你能根據需要進行重構。你是不是經常遇到一些不知是否該修改的代碼?種種顧慮讓你行動遲緩,過于保守,因為你不能保證所做的修改會不會破壞系統。如果有一套好的單元測試集,就能放心地進行重構,同時能保證你的代碼依然簡潔。
提高速度:編寫這么多測試會不會使開發速度減慢呢?人們經常會以速度(或開發開銷)作為反對進行TDD和使用xUnit框架的理由。所有新的工具都會有學習曲線,但是一旦開發人員適應了他們選擇的框架(通常只需很短的時間),開發速度實際上會加快。一個完備的單元測試集提供了一種方法對系統完成回歸測試,這說明,增加一個新特性之后,你不必因為懷疑它會不會破壞原系統而寢食難安。
提供反饋:單元測試還有一個經常被忽略的優點,即開發的節奏。盡管看上去好像無關緊要,但通過測試之后你會有一種完成任務的成就感!你不會成天地修改代碼而沒有任何反饋,這種測試—代碼—測試的方法會鼓勵你動作幅度小一些,通常修改一次代碼的時間僅幾分鐘而已。這樣你不會一下子看到冒出一大堆的新特性,而只是讓代碼基一次前進一小步。
從我們的經驗看,測試是會傳染的,你可能會慢慢上癮。一開始,許多開發人員都心存疑慮,但最終幾乎每個開發人員都迷上了運行測試后的綠條。測試第一次“抓住”bug或者增加一個新特性,只需幾分鐘而不是幾個小時,往往就是在這樣一些時候,開發人員會欣喜地認識到測試確實很有意義。