TDD并不是看上去的那么美

作者: coolshell  發布時間: 2011-02-28 21:38  閱讀: 1024 次  推薦: 1   原文鏈接   [收藏]  

  春節前的一篇那些炒作過度的技術和概念中對敏捷和中國ThoughtWorks的微辭引發了很多爭議,也驚動了中國ThoughtWorks公司給我發來了郵件想來找我當面聊聊。對于Agile的Fans們,意料之中地也對我進行了很多質疑和批評。我也回復了許多評論。不過,我的那些回復都是關于中國ThoughtWorks咨詢師以及其咨詢的方法的。我對Agile方法論中的具體內容評價的不是很多,所以,我想不妨討論一下Agile方法論中的具體的實踐(以前本站也討論過結對編程的利與弊)。

  那么,這次就說說TDD吧,這是ThoughtWorks中國和Agile的Fans們最喜歡的東西了。我在原來的那篇文章中,我把TDD從過度炒作的技術剔除了出去,因為我還是覺得TDD有些道理的,不過,回顧我的經驗,我也并不是很喜歡TDD。我這篇文章是想告訴大家,TDD并沒有看上去的那么美,而且非常難以掌控,并且,這個方法是有悖論之處的。

  TDD簡介

  TDD全稱Test Driven Development,是一種軟件開發的流程,其由敏捷的“極限編程”引入。其開發過程是從功能需求的test case開始,先添加一個test case,然后運行所有的test case看看有沒有問題,再實現test case所要測試的功能,然后再運行test case,查看是否有case失敗,然后重構代碼,再重復以上步驟。其理念主要是確保兩件事:

  • 確保所有的需求都能被照顧到。
  • 在代碼不斷增加和重構的過程中,可以檢查所有的功能是否正確。

  我不否認TDD的一些有用的地方,如果我們以Test Case 開始,那么,我們就可以立刻知道我們的代碼運行的情況是什么樣的,這樣可以讓我們更早地得到我們實現思路的反饋,于是我們更會有信心去重構,去重新設計,從而可以讓我們的代碼更為正確。

  不過,我想提醒的是,TDD和Unit Test是兩碼子事兒。有很多人可能混淆了自動化的Unit Test(如:XUnit系例)和TDD的軟件開發過程。另外,可能還會有人向鼓吹“TDD讓你進行自頂向下的設計方式”,對此,請參閱本站的《Richard Feynman, 挑戰者號, 軟件工程》——NASA的挑戰者號告訴你自頂向下設計的危險性。

  TDD的困難之處

  下面是幾個我認為TDD不容易掌控的地方,甚至就有些不可能(如果有某某TDD的Fans或是ThoughtWorks的咨詢師和你鼓吹TDD,你可以問問他們下面這些問題)

  • 測試范圍的確定。TDD開發流程,一般是先寫Test Case。Test Case有很多種,有Functional的,有Unit的,有Integration的……,最難的是Test Case要寫成什么樣的程度呢。
    • 如果寫的太過High Level,那么,當你的Test Case 失敗的時候,你不知道哪里出問題了,你得要花很多精力去debug代碼。而我們希望的是其能夠告訴我是哪個模塊出的問題。只有High Level的Test Case,豈不就是Waterfall中的Test環節?
    • 如果寫的太過Low Level,那么,帶來的問題是,你需要花兩倍的時間來維護你的代碼,一份給test case,一份給實現的功能代碼。
    • 另外,如果寫得太Low Level,根據Agile的迭代開發來說,你的需求是易變的,很多時候,我們的需求都是開發人員自己做的Assumption。所以,你把Test Case 寫得越細,將來,一旦需求或Assumption發生變化,你的維護成本也是成級數增加的。
    • 當然,如果我把一個功能或模塊實現好了,我當然知道Test 的Scope在哪里,我也知道我的Test Case需要寫成什么樣的程度。但是,TDD的悖論就在于,你在實現之前先把Test Case就寫出來,所以,你怎么能保證你一開始的Test Case是適合于你后面的代碼的?不要忘了,程序員也是在開發的過程中逐漸了解需求和系統的。如果邊實現邊調整Test Case,為什么不在實現完后再寫Test Case呢?如果是這樣的話,那就不是TDD了。
  • 關注測試而不是設計。這可能是TDD的一個弊端,就像《十條不錯的編程觀點》中所說的一樣——“Unit Test won’t help you write the good code”,在實際的操作過程中,我看到很多程序員為了趕工或是應付工作,導致其寫的代碼是為了滿足測試的,而忽略了代碼質量和實際需求。有時候,當我們重構代碼或是fix bug的時候,甚至導致程序員認為只要所有的Test Case都通過了,代碼就是正確的。當然,TDD的粉絲們一定會有下面的辯解:
    • 可以通過結對編程來保證代碼質量。
    • 代碼一開始就是需要滿足功能正確,后面才是重構和調優,而TDD正好讓你的重構和優化不會以犧牲功能為代價。

  說的沒錯,但僅在理論上。操作起來可能會并不會得到期望的結果。1)“結對編程”其并不能保證結對的兩個人都不會以滿足測試為目的,因為重構或是優化的過程中,一旦程序員看到N多的test cases 都failed了,人是會緊張的,你會不自然地去fix你的代碼以讓所有的test case都通過。2)另外,我不知道大家怎么編程,我一般的做法是從大局思考一下各種可行的實現方案,對于一些難點需要實際地去編程試試,最后權衡比較,挑選一個最好的方案去實現。而往往著急著去實現某一功能,通常在會導致的是返工,而后面的重構基本上因為前期考慮不足和成為了重寫。所以,在實際操作過程中,你會發現,很多時候的重構通常意味著重寫,因為那些”非功能性”的需求,你不得不re-design。而re-design往往意味著,你要重寫很多Low-Level的Test Cases,搞得你只敢寫High Level的Test Case。

  • TDD導致大量的Mock和Stub。相信我,Test Case并不一定是那么容易的。比如,和其它團隊或是系統的接口的對接,或是對實現還不是很清楚的模塊,等等。于是你需要在你的代碼中做很多的Mock和Stub,甚至fake一些函數來做模擬,很明顯,你需要作大量的 assumption。于是,你發現管理和維護這些Mock和Stub也成了一種負擔,最要命的是,那不是真正的集成測試,你的Test Case中的Mock很可能是錯的,你需要重寫他們。

  也許,你會說,就算是不用TDD,在正常的開發過程中,我們的確需要使用Mock和Stub。沒錯!的確是這樣的,不過,記住,我們是在實現代碼后來決定什么地方放一個Mock或Stub,而不是在代碼實現前干這個事的。

  • Test Case并沒有想像中的那么簡單。和Waterfall一樣,Waterfall的每一個環節都依賴于前面那個環節的正確性,如果我們沒有正確的理解需求,那么對于TDD,Test Case和我們的Code都會的錯的。所以,TDD中,Test Case是開發中最重要的環節,Test Case的質量的問題會直接導致軟件開發的正確和效率。而TW的咨詢師和Agile的Fans們似乎天生就認為,TDD比Waterfall更能準確地了解需求。如果真是這樣,用TDD進行需求分析,后面直接Waterfall就OK了。

  另外,某些Test Case并不一定那么好寫,你可能80%的編程時間需要花在某個Test Case的設計和實現上(比如:測試并發),然后,需求一變,你又得重寫Test Case。有時候,你會發現寫Test Case其實和做實際設計沒有差別,你同樣要考慮你Test Case的正確性,擴展性,易讀性,易維護性,甚至重用性。如果說我們開發的Test Case是用來保證我們代碼實現的正確性,那么,誰又來保證我們的Test Case的正確性呢?編寫Test Case也需要結對或是Code review嗎?軟件開發有點像長跑,如果把能量花在了前半程,后半程在發力就能難了。

  也許,TDD真是過度炒作的,不過,我還真是見過使用TDD開發的不錯的項目,只不過那個項目比較簡單了。更多的情況下,我看到的是教條式的生硬的TDD,所以,不奇怪地聽到了程序員們的抱怨——“自從用了TDD,工作量更大了”。當然,這也不能怪他們,TDD本來就是很難把控的方法。這里送給軟件開發管理者們一句話——“當你的軟件開發出現問題的時候,就像bug-fix一樣,首要的事是找到root cause,然后再case by case的解決,千萬不要因為有問題就要馬上換一種新的開發方法”。相信我,大多數的問題是人和管理者的問題,不是方法的問題。

1
0
 
標簽:TDD
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()