所有代碼都需要單元測(cè)試覆蓋嗎?
單元測(cè)試(unit testing)已經(jīng)越來越得到廣大開發(fā)者的認(rèn)可。作為低成本、速度快、穩(wěn)定度高的自動(dòng)化測(cè)試手段,單元測(cè)試可以在類和函數(shù)級(jí)別對(duì)代碼進(jìn)行質(zhì)量守護(hù),有助于避免尷尬、耗時(shí)的錯(cuò)誤。當(dāng)然,相比功能測(cè)試(Functional testing)和端到端測(cè)試(end-to-end testing),單元測(cè)試能夠寄予的產(chǎn)品級(jí)別的信心要略低一些,因而各個(gè)粒度的測(cè)試應(yīng)該是相輔相成的,互為補(bǔ)充。
常常聽到一些組織要求開發(fā)團(tuán)隊(duì)提高單元測(cè)試覆蓋率,換來的卻是怨聲載道,或者是一堆應(yīng)付差事的垃圾測(cè)試(沒有斷言的測(cè)試,都見過吧)。盡管,低測(cè)試覆蓋率意味著對(duì)質(zhì)量的信心不足,但是,單元測(cè)試覆蓋率真的要達(dá)到100%才好嗎?
100%聽起來肯定比95%要好,但是區(qū)別在于那些額外測(cè)試的價(jià)值對(duì)你可能是微不足道的。這要看哪種代碼沒有被測(cè)試覆蓋,以及你的測(cè)試能否暴露程序的錯(cuò)誤。100%的覆蓋率并不能夠確保沒有缺陷——它只能保證你所有的代碼都執(zhí)行了,而不管程序的行為是否滿足要求。與其追求代碼覆蓋率,不如將重點(diǎn)關(guān)注在確保寫出有意義的測(cè)試。
測(cè)試越多,額外測(cè)試的價(jià)值越少。第一個(gè)測(cè)試最有可能是針對(duì)代碼最重要的區(qū)域,因此帶來高價(jià)值與高風(fēng)險(xiǎn)。當(dāng)我們?yōu)閹缀跛惺虑榫帉憸y(cè)試后,那些仍然沒有測(cè)試覆蓋的地方很可能是最不重要和最不可能破壞的。
編寫一些測(cè)試是不費(fèi)腦筋的,但隨著我們接近完全的代碼覆蓋率,我們不那么確定了——我們差不多已經(jīng)為一切都編寫了測(cè)試,而剩下的沒有測(cè)試的代碼是微不足道,幾乎不會(huì)破壞。這就是所謂的收益遞減。要想從單元測(cè)試中獲得更多的收益,需要重新將單元測(cè)試從質(zhì)量工具定位成設(shè)計(jì)工具。TDD等方式可以幫助我們做到這一點(diǎn)。
換句話說,向代碼基增加100個(gè)精挑細(xì)選的自動(dòng)化測(cè)試是明顯的改善,但當(dāng)我們已有30000個(gè)測(cè)試時(shí),這些額外的100個(gè)測(cè)試就無足輕重了。
比賽場(chǎng)上的贏家是那些將注意力集中在賽場(chǎng)上的選手,而不是緊盯著計(jì)分板的人。—巴菲特
總之,當(dāng)你覺得生產(chǎn)環(huán)境中報(bào)來的bug很少了,或者你能夠自信地對(duì)代碼隨時(shí)進(jìn)行修改,單元測(cè)試就已經(jīng)足夠多了。
另一方面,也有些觀點(diǎn)認(rèn)為,不但不值得追求高覆蓋率,甚至寫單元測(cè)試本身就是非常耗時(shí)和難以維護(hù)的重復(fù)工作。這種極端觀點(diǎn)我同樣不贊同。
代碼覆蓋率不能告訴我們代碼質(zhì)量的高低,也不能用了評(píng)判開發(fā)人員的績(jī)效。但它能夠告訴我們哪些代碼還沒有被測(cè)試覆蓋,哪里有漏網(wǎng)之魚。至于這魚值不值得抓,還是取決于開發(fā)人員的經(jīng)驗(yàn)進(jìn)行風(fēng)險(xiǎn)判斷。那么,接下來我們?cè)賮矸治鲆幌拢降啄男~值得抓。
unit-test-quadrants
圖中,產(chǎn)品代碼可以分為四個(gè)類別,縱軸是從單元測(cè)試中得到的收益,橫軸是單元測(cè)試的成本,我們從投入產(chǎn)出的角度來分析,到底哪些代碼適合于進(jìn)行單元測(cè)試:
瑣碎且無甚依賴的代碼。比如getter/setter, 比如簡(jiǎn)單地調(diào)用系統(tǒng)時(shí)間,比如 toString()等等,基本是不需要測(cè)試的。雖然測(cè)起來容易,但我們有信心說它們出錯(cuò)的概率也非常低,測(cè)這種代碼的確索然無味。
承上啟下的代碼。比如用MVC框架實(shí)現(xiàn)的代碼里,某些service層只是簡(jiǎn)單地被Action層調(diào)用,然后轉(zhuǎn)發(fā)到下一層去。這種粘合代碼不具備太多被測(cè)試的價(jià)值,而且由于是銜接上下兩層的傳話筒,測(cè)起來卻需要對(duì)周圍各層進(jìn)行mock或打樁。要想驗(yàn)證其所做的那一點(diǎn)點(diǎn)工作,其實(shí)還挺麻煩的。當(dāng)然,也有一些觀點(diǎn)比如”London School TDD”堅(jiān)持認(rèn)為,對(duì)于企業(yè)級(jí)應(yīng)用,就要像制作香腸一樣,一層一層地對(duì)交互(interaction-based)進(jìn)行測(cè)試,每測(cè)一層都需要mock的幫助。
具有算法和業(yè)務(wù)邏輯的代碼。比如排序或處理數(shù)據(jù)等代碼,這些是最值得進(jìn)行單元測(cè)試的代碼了。雖然有一定的成本,但是由于算法邏輯的輸入輸出非常確定,結(jié)構(gòu)復(fù)雜且具有業(yè)務(wù)價(jià)值,外部依賴較少,在這上面投入是一定可以得到豐厚回報(bào)的。這即是”Classic TDD”觀點(diǎn),對(duì)狀態(tài)進(jìn)行測(cè)試(state-based)。
過于復(fù)雜的代碼。充滿復(fù)雜邏輯、交織在一起、散發(fā)著各種壞味道的遺留代碼,就像一座未開發(fā)的金礦,你需要做很多清理工作,才能順利地進(jìn)行開采,否則將寸步難行,不知從何下手,而且成本極高。這種代碼建議先進(jìn)行粗粒度重構(gòu)和接口測(cè)試(孰先孰后要根據(jù)實(shí)際情況),破除掉嚴(yán)重的依賴,找到所謂的接縫(Seam),將具有邏輯的部分與承上啟下的代碼分離開,然后立即將單元測(cè)試注入到接縫中,形成對(duì)有邏輯代碼的保護(hù)網(wǎng),隨后繼續(xù)縮小重構(gòu)的范圍,不斷地添加測(cè)試和重構(gòu),逐漸滲透形成一張網(wǎng),將又臭又硬的代碼塊切碎。
除了3)以外的代碼怎么測(cè)?可以采用其他測(cè)試手段,比如功能性測(cè)試來進(jìn)行粗粒度覆蓋,同樣能提升對(duì)產(chǎn)品的信心,并且成本更低,特別適合敏捷迭代式開發(fā),能夠在有限的時(shí)間盒內(nèi)達(dá)到預(yù)期的質(zhì)量水平。
此外,持續(xù)地重構(gòu)代碼,同時(shí)編寫更有效的單元測(cè)試,也是敏捷開發(fā)者應(yīng)該具備的基本功,有助于提高投入產(chǎn)出比。
posted on 2014-04-02 10:56 順其自然EVO 閱讀(290) 評(píng)論(0) 編輯 收藏 所屬分類: 測(cè)試學(xué)習(xí)專欄