TDD並不是看上去的那麼美

2021-09-06 17:53:52 字數 3861 閱讀 3231

原文:

春節前的一篇

那些炒作過度的技術和概念

結對程式設計的利與弊)。

那麼,這次就說說tdd吧,這是thoughtworks中國和agile的fans們最喜歡的東西了。我在

原來的那篇文章

中,我把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的fans或是thoughtworks的諮詢師和你鼓吹tdd,你可以問問他們下面這些問題)

測試範圍的確定。tdd開發流程,一般是先寫test case。test case有很多種,有functional的,有unit的,有integration的……,最難的是test case要寫成什麼樣的程度呢。

如果寫的太過high level,那麼,當你的test case 失敗的時候,你不知道**出問題了,你得要花很多精力去debug**。而我們希望的是其能夠告訴我是哪個模組出的問題。只有high level的test case,豈不就是wate***ll中的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並沒有想像中的那麼簡單。和wate***ll一樣,wate***ll的每乙個環節都依賴於前面那個環節的正確性,如果我們沒有正確的理解需求,那麼對於tdd,test case和我們的code都會的錯的。所以,tdd中,test case是開發中最重要的環節,test case的質量的問題會直接導致軟體開發的正確和效率。而tw的諮詢師和agile的fans們似乎天生就認為,tdd比wate***ll更能準確地了解需求。如果真是這樣,用tdd進行需求分析,後面直接wate***ll就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的解決,千萬不要因為有問題就要馬上換一種新的開發方法」。相信我,大多數的問題是人和管理者的問題,不是方法的問題。

虛擬座談會:tdd有多美?

TDD並不是看上去的那麼美

那麼,這次就說說tdd吧,這是thoughtworks中國和agile的fans們最喜歡的東西了。我在原來的那篇文章中,我把tdd從過度炒作的技術剔除了出去,因為我還是覺得tdd有些道理的,不過,回顧我的經驗,我也並不是很喜歡tdd。我這篇文章是想告訴大家,tdd並沒有看上去的那麼美,而且非常難以掌...

TDD並不是看上去的那麼美

那麼,這次就說說tdd吧,這是thoughtworks中國和agile的fans們最喜歡的東西了。我在原來的那篇文章中,我把tdd從過度炒作的技術剔除了出去,因為我還是覺得tdd有些道理的,不過,回顧我的經驗,我也並不是很喜歡tdd。我這篇文章是想告訴大家,tdd並沒有看上去的那麼美,而且非常難以掌...

TDD並不是看上去的那麼美

那麼,這次就說說tdd吧,這是thoughtworks中國和agile的fans們最喜歡的東西了。我在原來的那篇文章中,我把tdd從過度炒作的技術剔除了出去,因為我還是覺得tdd有些道理的,不過,回顧我的經驗,我也並不是很喜歡tdd。我這篇文章是想告訴大家,tdd並沒有看上去的那麼美,而且非常難以掌...