原創(chuàng)文章渺蒿,轉(zhuǎn)載請注明出處
作為一個實際寫代碼的Coder,老代碼能不碰就不碰---我舉雙手贊成值纱,既沒有UT鳞贷,邏輯又混在一起,天知道改完以后會出什么Bug虐唠。
但是對于團(tuán)隊來講搀愧,如果明確知道這個模塊無法測試、無法被很好的修改疆偿,那么是時候把這部分代碼提上重構(gòu)的日程了咱筛。
一. 什么是Unit Test
直譯:面向獨立單元的測試方法。
單單這一句話我們心里有就有了疑問:什么是獨立單元杆故,或者什么樣的代碼才算一個獨立單元迅箩?
為了回答這個問題,這里引用一下wikipedia的解釋:
Intuitively, one can view a unit as the smallest testable part of an application.
https://en.wikipedia.org/wiki/Unit_testing
我們來抓一下重點:
- 單元处铛,對于不同的編程語言饲趋,單元的定義并不相同拐揭。在面向過程的語言中(C等),單元通常被定義為一個函數(shù)(Function)奕塑;而在面向?qū)ο蟮木幊陶Z言中(Java等)堂污,單元往往指一個類(也可指代具體的方法Method)。
- 最小的爵川,小到無法再拆分敷鸦,這個單元只做了一件事。
- 可測試的寝贡,這個單元應(yīng)該有明確的輸入和輸出扒披,并且在輸入確定時,我們應(yīng)該能夠預(yù)測輸出的情況圃泡。
二. Unit Test要達(dá)到的目標(biāo)
從工程師的角度來看碟案,一些童鞋可能會覺得寫Unit Test是枯燥切無意義的。工程期就那么幾天颇蜡,產(chǎn)品經(jīng)理又不停的改功能价说,哪有心情和時間來寫UT?
這種想法可以說對也可以說不對风秤。首先鳖目,面對有限的時間和變動的需求,為所有代碼寫UT是不現(xiàn)實的缤弦。但是不是UT就真的是枯燥且無意義的呢---也不是领迈。至少從我的角度來看,一個項目的UT需要起到以下三個作用碍沐。
- 檢查出明顯的Bug
- 為如何使用函數(shù)(單元)提供樣例
- 為了重構(gòu)
可能有人會說狸捅,為了檢查Bug,這不廢話嗎累提。尘喝。。當(dāng)然我相信在座的各位對于如何在UT中檢查Bug都有兩把刷子的斋陪,而這恰好也不是我想討論的重點朽褪,但我們還是需要注意:UT并不能檢查出所有Bug,對于無法預(yù)知的用戶輸入无虚,UT能做的只是在我們所考慮到的情況下鞍匾,避免一些明顯的錯誤。但是對我們沒有考慮到的情況-----告辭骑科。橡淑。。
這里著重討論一下后面兩點:
提供樣例咆爽,有的童鞋習(xí)慣在函數(shù)上面寫上一大段的注釋梁棠,有時候看業(yè)務(wù)代碼搞得像在讀JDK源碼置森。出現(xiàn)這種情況,我認(rèn)為可能有兩個原因:
第一符糊,代碼沒有做好拆分工作凫海,將過多的邏輯糅合在一個類(方法)中,需要考慮的輸入情況異常多男娄,直觀上根本無從下手來寫UT行贪。
針對這種情況,我們應(yīng)該考慮使用一些設(shè)計手法將代碼拆分成獨立的可測試的最小單元模闲。
第二建瘫,這個單元已經(jīng)是最小的了,但是它確實有許多邏輯需要處理尸折。首先啰脚,我認(rèn)為如果真的拆分成了最小單元,那需要處理的邏輯應(yīng)該并不會太多实夹,至少不需要寫一段話來描述它要做的事情橄浓。即使需要告知后來的開發(fā)這里做了什么,一個完整的UT也比閱讀一大段文字來的實際亮航。為了重構(gòu)
許多工程師對重構(gòu)這個詞有個錯誤的印象---提到重構(gòu)荸实,我們就需要對代碼做翻天覆地的變化,甚至覺得是把以前的代碼廢棄掉重新寫缴淋。
最近閱讀了一些重構(gòu)方面的書籍准给,有一個讓我印象深刻的觀點:重構(gòu)應(yīng)該要貫穿整個產(chǎn)品的生命周期。有因為業(yè)務(wù)擴(kuò)展與變動進(jìn)行重構(gòu)宴猾,也有一個Bug導(dǎo)致需要對代碼結(jié)構(gòu)進(jìn)行重構(gòu)。而但凡涉及到重構(gòu)叼旋,我們就需要UT為我們保駕護(hù)航仇哆。
在對代碼進(jìn)行重構(gòu)時,UT就像一張保護(hù)網(wǎng)夫植,防止我們破壞掉原有的邏輯讹剔。對于一個規(guī)模不大的核心模塊,在上線之初我們就為它編寫了詳細(xì)的UT用例详民,使其滿足我們所考慮的各種情況延欠。但是隨著業(yè)務(wù)的增長,我們可能需要對模塊內(nèi)部進(jìn)行優(yōu)化沈跨,使其有更高的性能由捎。得益于我們之前的工作,每重構(gòu)完一段代碼饿凛,我們都運(yùn)行一遍UT狞玛,如果通過软驰,大概率說明我們沒有“搞破壞”。當(dāng)整個部分重構(gòu)完成心肪,我們需要反過來對UT進(jìn)行審視锭亏,之前沒有考慮到的情況、新的代碼都需要進(jìn)行覆蓋硬鞍。
這樣我們的代碼保護(hù)網(wǎng)就會越來越牢固慧瘤。可以想象固该,如果沒有UT锅减,我們新來的開發(fā)人員在修改老功能時是多么的緊張,因為他不知道自己也許不經(jīng)意間破壞了原有的邏輯蹬音。
三. 哪些地方需要Unit Test
正如前文所述上煤,并不是所有地方都需要寫UT,從某種意義上講著淆,UT應(yīng)該越少越好劫狠,前提是系統(tǒng)拆分夠精確,關(guān)注點的測試夠詳細(xì)永部。
針對實際的項目独泞,我對代碼進(jìn)行了一些分類:
- 瑣碎的代碼,例如getter/setter,toString苔埋。除非有特殊邏輯懦砂,否則為這些代碼寫UT沒有意義。
- 無明確意義组橄、承上啟下的代碼荞膘,例如一些只做透傳作用的Service等。(當(dāng)然玉工,個人認(rèn)為這種只做透傳的代碼應(yīng)該盡量少)
- 具有算法和業(yè)務(wù)邏輯的代碼羽资。這部分代碼我們應(yīng)該重點關(guān)注,最為典型的就是一些公共類遵班,基礎(chǔ)Service層等屠升。盡量為它們寫上詳盡的UT,造福后人狭郑。
- 最后一類是非常復(fù)雜的代碼腹暖。各種復(fù)雜邏輯交織在一起,牽一發(fā)而動全身翰萨,我們本能地可以感覺到代碼中的“壞味道”脏答。對于這類代碼,首先應(yīng)該考慮做代碼的清理工作(由粗到細(xì)重構(gòu)),過程中不斷為小單元注入Unit Test以蕴,最后形成一張嚴(yán)密的代碼保護(hù)網(wǎng)糙麦,將原來的代碼切碎為可維護(hù)的代碼。
在實際開發(fā)中對第四點可能有些爭議丛肮。對于公司或者團(tuán)隊來講赡磅,一個產(chǎn)品的代碼不可能始終是一個人負(fù)責(zé),但往往新的同事接手老代碼時不知道從哪下手宝与。我的第一份工作焚廊,當(dāng)我作為一個畢業(yè)生,第一次接手一個完整的項目代碼习劫,一上來就是一個好幾千行的函數(shù)咆瘟,其中包含的各種請求的發(fā)送,變量的隨意命名與重復(fù)使用诽里,還有各種復(fù)制粘貼的痕跡袒餐。你能想象對于一個新人來講這是多么的絕望嗎。
作為一個實際寫代碼的Coder谤狡,老代碼能不碰就不碰---我舉雙手贊成间涵,既沒有UT匹中,邏輯又混在一起秸妥,天知道改完以后會出什么Bug七蜘。
但是對于團(tuán)隊來講,如果明確知道這個模塊無法測試捕仔、無法被很好的修改匕积,那么是時候把這部分代碼提上重構(gòu)的日程了。
其實我認(rèn)為代碼的組織與規(guī)范(包括UT)是開發(fā)流程中非常重要的一部分榜跌,在一開始我們就應(yīng)該定期進(jìn)行CodeReview闪唆,避免寫出無法維護(hù)的代碼。一個團(tuán)隊可能會負(fù)責(zé)多個項目钓葫,這些項目應(yīng)該是屬于團(tuán)隊中的每一個人悄蕾,而不是誰負(fù)責(zé),其他人就可以不管瓤逼。我們應(yīng)該把項目的Dev和Ops視為自己的責(zé)任笼吟。有太多的項目由于走形式的CodeReview库物、沒有UT霸旗、沒有文檔,導(dǎo)致最后無法維護(hù)戚揭,這是我們都不想看到的诱告。
UT是不可能寫的,這輩子都不可能寫的民晒,老代碼就只能讓它勉強(qiáng)運(yùn)行下去這樣子~
四. 怎么做Unit Test
至于如何做UT精居,網(wǎng)上一搜一大把的教程锄禽,這里就不贅述了,只是列幾條個人總結(jié)的值得注意的點:
- 多組數(shù)據(jù)(考慮周全)->正常業(yè)務(wù)測試靴姿,邊界條件測試
- 不要誤用Mock工具沃但,理清需要被Mock的對象
- 整體覆蓋率沒有意義,但是關(guān)鍵業(yè)務(wù)代碼覆蓋率很重要
- 運(yùn)維時:每一個BugReport都應(yīng)有一個對應(yīng)的UT
- UT并不是越多越好佛吓,而是對于核心代碼宵晚、有意義的錯誤,UT越詳細(xì)越好