所以,在敏捷環境中,軟件開發初期應該通過文檔和用例等手段大致表達需求,實現之后在實際運行中體驗效果,不斷優化探索和明確需求和外部環境,當需求和對外部環境的認識達到一個比較穩定的程度才編寫測試用例將需求固化下來。
上面的論述主要針對貼近最終用戶的外部需求(如ATDD),下面我會進一步解釋即使是在內部的單元測試級別TDD仍然有問題。我們還是首先從需求入手,思考一下單元的需求是哪里來的呢?答案是:需求來自于設計!比如,對輪胎的需求來源于汽車的設計,低層模塊的需求來源于高層模塊的設計。
而在開發初期,這種內部設計具有很大的不穩定性,帶有很多假設的成分,在沒有進行集成測試的情況下,很難講這種內部設計是否合理。實際項目開發通常會在集成運行之后不斷調整內部的設計,即影響單元的需求。那么,如果是測試驅動,首先按不成熟的內部設計把一個個單元需求編寫成單元測試再來實現,實際上大大推遲了能進行集成測試的時間,對于真正快速弄清高層需求穩定設計反而是不利的。
假設最終還是所有單元都完成,然后開始運行集成或驗收測試,這時候有兩種可能:
1. 用戶看到實際效果,決定調整需求;
2. 發現集成前在單元層面的假設不成立或者是有沒有考慮到的情況。不論是哪一種情況發生,以前所寫的單元測試都面臨著被廢棄或必須修改的命運。實際上,多數與業務相關的單元測試用例比起集成或驗收測試用例更加不穩定,因為它會受到所有其上層模塊的需求和設計變動的影響。
由于我們在不穩定的單元測試上浪費了大量的時間(按我的經驗編寫單元測試比編寫實現更耗時),這就導致了遲遲無法進行集成看到實際效果,也沒有辦法敏捷地應對需求的調整。也就是說具有諷刺意味的,Test First理念居然是和敏捷理念矛盾的!
所以,我認為Test First不符合敏捷開發的基本假設,而真正符合敏捷的理念是需求和設計依賴于實現的反饋,需要在實際運行過程中根據效果不斷探索調整得來的,不可能脫離實際運行寫出真正符合最終需求的測試用例來。所以,我們真正應該做的是盡快看到實際運行的效果,而自動化測試作為固化的需求和設計是在看到效果之后。在集成之前花太多精力進行測試驅動只會導致遲遲看不到實際運行效果(尤其是基于開發人員自己的假設編寫大量單元測試用例),看到效果需要調整需求又會廢掉或改掉一大堆的測試用例。
實際上,越是外部的需求其變更帶來的影響和代價越大,越是需要盡早明確。從宏觀上看,TDD所謂的快速反饋實際上是加快內部反饋,延遲了外部反饋,這無異于本末倒置。而大量需要修改或作廢的測試用例其實是一種很大的浪費,這和消除浪費的精益思想也是矛盾的!
上面這幅cost/length_of_feedback_cycle圖是我們常見的用于說明敏捷方法比傳統方法具有更短的反饋周期,更小代價的應對變化。從圖中我們可以清晰的看到在驗收測試中發現的需求錯誤導致的代價是最高的。如果驗收測試往后推遲一點,發現錯誤的代價將按非線性地增長。上面我們已經論述了,任何方法都不可能消除驗收測試后對需求的調整,因為這是需求產生的正常過程。
我們唯一可以做的是盡可能地縮短驗收測試的反饋周期,但是很不幸TDD大量的內部測試只會導致推遲驗收測試的時間,從而大大增加代價。在實際開發中,我提倡在第一次集成運行測試之前不要寫單元測試用例;自動化的驗收測試用例則視編寫和維護的代價而定,如果代價比較高,則應該采用文檔和Use Case來描述需求,因為這兩種方式比自動化的驗收測試更容易維護。編寫單元測試一定是在集成以后,這樣才能首先得到外部反饋,盡量先保證做正確的事情,再正確地做事。
下面這段話來自于InfoQ文章《Mock不是測試的銀彈》:在使用JMock框架后測試編寫起來更容易,運行速度更快,也更穩定,然而出乎意料的是產品質量并沒有如我們所預期的隨著不斷添加 的測試而變得愈加健壯,雖然產品代碼的單元測試覆蓋率超過了80%,然而在發布前進行全面測試時,常常發現嚴重的功能缺陷而不得不一輪輪的修復缺陷、回歸 測試。為什么編寫了大量的測試還會頻繁出現這些問題呢? 這描述的情況和我在實踐中遇到的情況類似,不過很可惜文章并沒有找到問題真正的原因。
真正的原因不是什么Mock不Mock,而是TDD 的單元測試是基于開發人員的假設,這些假設的測試即使全部通過代碼覆蓋率100%,到了集成測試發現假設根本不成立或者原先在單元層面很多情況沒有考慮到,這又怎能保證高質量?在TDD的實踐者中我見到過不少類似這樣的,他們很認真,編寫了很多單元測試用例,代碼覆蓋率也很高,但他們其實是有意無意在先正確地做事(單元測試),再做正確的事(集成測試),這就是本末倒置。
當然,我不是全盤否定TDD。TDD在某些需求比較固定的場合是適用的,尤其是與具體業務關系不大的需求,比如:寫一個通用的數據結構,實現一個通用算法。TDD的先關注需求和思考外部接口設計的理念也對促進開發人員的抽象思維有很大益處。
另外,TDD通常也具有較高的代碼覆蓋率。本文的主要觀點在于:實際項目中,由于用戶需求不確定性和外部環境不確定性,不要期望可以在實現之前完全明確需求,需求是在實際運行看到效果之后才逐步明確的;我們的開發過程必須能夠敏捷地適應需求的變化,而TDD的Test First理念恰好與之矛盾。
所以,對于TDD不了解的朋友,我建議應該學習和實踐TDD,從而獲得其益處;同時我也提醒TDD存在理論上的缺陷,這是在實踐中需要特別留意的。
原文轉自:http://kb.cnblogs.com/page/120057/