一、不寫單元測試的理由
工作幾年,遇到過不少抗拒寫單元測試的人,總結一下大致有以下這么幾個理由:
首先,寫單元測試所支出的時間可能比實現功能本身所花費的時間還多。言外之意,在實現完所有功能之前不值得寫單元測試。如果現階段功能開發大致完畢,可能也不會去補上虧欠的單元測試,理由大致有這么幾點:
- 需求總是無窮盡的,還有下階段功能需求要實現,沒空補單元測試。
- 要補的單元測試太多,無從下手,主觀上抗拒。
- 單元測試編寫難度大。一方面原因可能是功能函數實現上不夠合理,另一方面是沒有(或者不知道)好用的單元測試框架和mock框架。
- 單元測試不算入工作量內。
其次,功能需求還不穩定,寫單元測試的性價比不高。換句話說,萬一明天需求一變,那不光功能代碼廢了,單元測試也廢了。如果不寫單元測試,那這部分工夫就不會白費。
二、寫單元測試的投入產出比
以投入產出比
作為這個問題的判斷依據,我覺得是所有理性人都會做出的選擇。而既然引入了投入產出比這個經濟學上的概念,那么應該也能接受另一個經濟學上的普世規律——邊際收益遞減。以及資源(主要指時間和精力)具有稀缺性這一規律。
下面的分析都將是建立在以上這些共識的基礎之上,如果你并不認同這些規律同樣可以用來分析軟件開發過程中值不值得寫單元測試
這個問題,那么閱讀以下內容僅僅是在浪費你寶貴的時間。如果你繼續往下閱讀,那么,我將假設你已經和我達成了這些共識。
雖然要為這個問題做精確的投入產出比定量分析比較困難,但并不妨礙我們通過定性分析來得出自己心中的答案。
成本(投入)
- 編寫單元測試用例所額外付出的時間,短期內會拖慢項目進度。
收益(產出)
提升代碼質量。監督開發人員寫出更加易于測試和可維護的代碼。
提升開發團隊內部的協作效率。其他開發人員可以通過閱讀單元測試用例來理解代碼原作者的意圖。
保證功能實現的長期穩定。代碼一旦發生與原功能意圖不相符的變化,通過跑單元測試可以體現出來,即可以防止功能被無意識地破壞。
提高自動化測試占比,降低其他測試方式上的投入。
從上面我所羅列的成本/收益數量上說,收益的數量遠大于成本。但是,分析投入產出比并不是掰手指數數量就能得出答案的,特此說明。
首先,短期項目不寫單元測試是劃算的選擇。至于到底多長時間算作短期
,并無定論。我選擇將一個月
作為劃分的界限,一月以內為短期,多于一月是中期或者長期,至于中長期的界限在哪兒,這個無關緊要,暫且不論。
短期項目的典型代表就是演示用demo項目。這類項目的共性是時間緊迫,項目生命周期短,無需后續的維護,使用一次性(或者接近一次性)。如果說中長期項目的使用周期(區別于開發周期)是時間段的話,那么短期項目的使用周期就是一個時間點或者幾個時間點。這種情況下將有限的資源投入到單元測試中,所獲得的收益并不明顯。如果繼續追加投入,由于收益增幅平緩,投入產出比極低,甚至為負。索性零投入零產出,雖然略顯保守,但也不失為一個好的選擇。
其次,中長期項目不寫單元測試絕對不是劃算的選擇。請注意,這里我并沒有說中長期項目寫單元測試是劃算的選擇
這樣的話。因為寫單元測試
這個說法太寬泛。很明顯,單元測試覆蓋率1%
與99%
是有巨大區別的,而這兩者都屬于寫單元測試
這個范疇。
對于中長期項目,將資源投入到單元測試上所獲得的收益曲線會是這樣(不太會畫圖,暫且用文字描述):
橫坐標為投入,縱坐標為產出。
開始階段,隨著投入的增加,收益(產出)增幅明顯,曲線比較陡峭。
當投入到達某一臨界值,單位投入所獲得的收益最大。
當投入繼續追加并超過臨界值,收益的增幅明顯放緩,曲線開始變得平緩,單位投入所獲得的收益越來越少,即邊際收益遞減。
如果你也認可以上的投入產出比曲線,那么不難推出以下幾個結論:
不寫單元測試一定不是最佳選擇。
不寫單元測試
可以理解為是零投入/零成本,那么必然的也會是零收益。寫單元測試并且單元測試的覆蓋率接近100%也一定不是最佳選擇。由于邊際收益遞減,投入一旦越過臨界值,那么繼續追加投入所帶來的收益將越來越少。
臨界值才是理論上的最佳選擇。
上面提到的臨界值
是屬于寫單元測試
的范疇,并且單元測試覆蓋率在0%~100%之間的某個點。所以說,軟件開發過程中值不值得寫單元測試
這個問題的答案應該無需再多言了。
文章一開始提到的那些不寫單元測試的理由,往往是抱著一個非黑即白
的觀點,認為不寫單元測試的反面就是寫單元測試且覆蓋率近100%。因而會過分夸大寫單元測試的成本(單元覆蓋率100%的代價當然大),選擇短期利益。
三、個人的做法
下面以個人的小開源項目gbb為例,啰嗦幾句我自己寫單元測試的習慣。
開源項目一定要寫單元測試!開源項目一定要寫單元測試!開源項目一定要寫單元測試!按照規定,重要的事情得說三遍。除了上文提到的單元測試的好處外,單元測試也是開源項目負責任的一種體現。我花了時間去構思各種情況下的測試用例,我能為開源項目質量負責。
不是非得寫完一個函數就必須立馬寫單元測試或者TDD。關于寫單元測試的時機,我一般選擇在完成一個基本功能后寫單元測試,優先覆蓋功能的主體邏輯,一些邊界條件邏輯不會體現在這個階段的單元測試當中。我覺得這個階段也是投入產出比最大的階段。
等功能相對穩定后,再把一些邊界條件邏輯納入到單元測試當中。這是單元測試覆蓋率提升較大的階段。
最后一個階段是刷數據階段(gbb項目單元測試覆蓋率曲線)。所謂的
刷數據階段
的主要目標就是為了讓覆蓋率盡可能提高。刷數據并不是在某個功能成熟穩定后開始,我是選擇在開源項目進入成熟階段后開始刷單元測試覆蓋率,為的就是使其更加好看。對于非開源項目,建議選擇跳過這個摳細節的階段。
四、參考
在寫這篇博客時,我并沒有查閱相關的這類文章。為的就是防止他人的觀點在不知不覺中摻入其中,影響了我的原始觀點。