單元測(cè)試(模塊測(cè)試)是開(kāi)發(fā)者編寫的一小段代碼,用于檢驗(yàn)被測(cè)代碼的一個(gè)很小的、很明確的功能是否正確。
設(shè)想一下,當(dāng)你開(kāi)發(fā)完一個(gè)功能模塊的時(shí)候,你如何確定你的模塊沒(méi)有 bug 呢?如果涉及到具體的業(yè)務(wù),你會(huì)執(zhí)行 debug 模式,然后一點(diǎn)一點(diǎn)的深入到代碼中去查看嗎?
其實(shí)我們每天都在做單元測(cè)試。你寫了一個(gè)函數(shù),除了極簡(jiǎn)單的外,總是要執(zhí)行一下,看看功能是否正常,有時(shí)還要想辦法輸出些數(shù)據(jù),如彈出信息窗口什么的,這,也是單元測(cè)試,把這種單元測(cè)試稱為臨時(shí)單元測(cè)試。只進(jìn)行了臨時(shí)單元測(cè)試的軟件,針對(duì)代碼的測(cè)試很不完整,代碼覆蓋率要超過(guò)70%都很困難,未覆蓋的代碼可能遺留大量的細(xì)小的錯(cuò)誤,這些錯(cuò)誤還會(huì)互相影響,當(dāng)BUG暴露出來(lái)的時(shí)候難于調(diào)試,大幅度提高后期測(cè)試和維護(hù)成本,也降低了開(kāi)發(fā)商的競(jìng)爭(zhēng)力??梢哉f(shuō),進(jìn)行充分的單元測(cè)試,是提高軟件質(zhì)量,降低開(kāi)發(fā)成本的必由之路。
對(duì)于程序員來(lái)說(shuō),如果養(yǎng)成了對(duì)自己寫的代碼進(jìn)行單元測(cè)試的習(xí)慣,不但可以寫出高質(zhì)量的代碼,而且還能提高編程水平。
要進(jìn)行充分的單元測(cè)試,應(yīng)專門編寫測(cè)試代碼,并與產(chǎn)品代碼隔離。我認(rèn)為,比較簡(jiǎn)單的辦法是為產(chǎn)品工程建立對(duì)應(yīng)的測(cè)試工程,為每個(gè)類建立對(duì)應(yīng)的測(cè)試類,為每個(gè)函數(shù)(很簡(jiǎn)單的除外)建立測(cè)試函數(shù)。
剖析
- 不知道怎么編寫單元測(cè)試
這個(gè)問(wèn)題在于,還沒(méi)有接觸過(guò)單元測(cè)試,同時(shí),也沒(méi)有體會(huì)過(guò)企業(yè)級(jí)的代碼開(kāi)發(fā)。不知道同時(shí)也不了解單元測(cè)試能帶給你什么。設(shè)想一下,當(dāng)你開(kāi)發(fā)完一個(gè)功能模塊的時(shí)候,你如何確定你的模塊沒(méi)有 bug 呢?如果涉及到具體的業(yè)務(wù),你會(huì)執(zhí)行 debug 模式,然后一點(diǎn)一點(diǎn)的深入到代碼中去查看嗎?如果你一直都是這樣,那么你早就已經(jīng) OUT 了。趕快去了解一下單元測(cè)試的工具吧,你會(huì)收獲很大的。
- 項(xiàng)目沒(méi)有要求,所以不編寫
這個(gè)問(wèn)題反映出了一種現(xiàn)象,同時(shí)也是一種習(xí)慣。項(xiàng)目有沒(méi)有要求,只能說(shuō)明項(xiàng)目的管理上不嚴(yán)格,并不是程序員不編寫單元測(cè)試的理由。他們?cè)谝酝拈_(kāi)發(fā)中,并沒(méi)有養(yǎng)成寫單元測(cè)試的好習(xí)慣。可想而知,他們的代碼質(zhì)量,我就不敢恭維了。給個(gè)建議,嘗試著寫漂亮的代碼,之所以因?yàn)槠?,是指得健康、?jiǎn)潔、健壯。當(dāng)然,完成漂亮的代碼就離不開(kāi)單元測(cè)試了。
- 單元測(cè)試價(jià)值不高,完全是浪費(fèi)時(shí)間
這種說(shuō)法其實(shí)是錯(cuò)誤的。為什么這么說(shuō)呢?在日常的開(kāi)發(fā)中,代碼的完工其實(shí)并不等于開(kāi)發(fā)的完工。如果沒(méi)有單元測(cè)試,那么如何保證代碼能夠正常運(yùn)行呢?測(cè)試人員做的只是業(yè)務(wù)上的集成測(cè)試,也就是黑盒測(cè)試,對(duì)單個(gè)的方法是沒(méi)有辦法測(cè)試的,而且,測(cè)試出的 bug 的范圍也會(huì)很廣,根本不能確定 bug 的范圍,還得去花時(shí)間來(lái)確定 bug 出在什么地方。難道這就不浪費(fèi)時(shí)間了嗎?甚至,這樣的方式,時(shí)間浪費(fèi)的會(huì)更多。
- 業(yè)務(wù)邏輯比較簡(jiǎn)單,不值得編寫單元測(cè)試
所謂的業(yè)務(wù)邏輯比較簡(jiǎn)單,其實(shí)是相對(duì)的。當(dāng)你對(duì)某一塊業(yè)務(wù)邏輯很熟悉的時(shí)候,你自然會(huì)認(rèn)為它很簡(jiǎn)單。然而,單元測(cè)試的必要性并不是僅僅在于測(cè)試代碼的功能是否正確,還在于,當(dāng)其他同事在了解你的業(yè)務(wù)的時(shí)候,能夠很快的通過(guò)單元測(cè)試來(lái)熟悉代碼的功能,甚至不用去讀代碼,就能夠知道它做了哪些事情。因此,寫單元測(cè)試不僅是解放了自己,更方便了別人。
- 項(xiàng)目前期還在盡量寫測(cè)試,到了后期就失控了
這種問(wèn)題的原因在于,對(duì)項(xiàng)目進(jìn)度、項(xiàng)目中的技術(shù)點(diǎn)研究時(shí)間、人員的溝通、業(yè)務(wù)需求的熟悉程度等沒(méi)有把控好。這個(gè)問(wèn)題的出現(xiàn)并不是個(gè)人的問(wèn)題,而是反映了項(xiàng)目管理中存在的問(wèn)題。當(dāng)然,個(gè)人的原因也存在,就是如何在有限的時(shí)間里,提高效率。這一點(diǎn)需要大家好好思考一下了。我的建議,多做計(jì)劃,根據(jù)實(shí)際情況變更計(jì)劃。多和項(xiàng)目組長(zhǎng)、組成員進(jìn)行溝通。及時(shí)反應(yīng)項(xiàng)目中存在的問(wèn)題。
- 為了完成編碼任務(wù),沒(méi)有足夠的時(shí)間編寫單元測(cè)試
這個(gè)問(wèn)題在于,程序員領(lǐng)取的任務(wù)較為復(fù)雜,或者自己的開(kāi)發(fā)效率有待提高。其實(shí),開(kāi)發(fā)任務(wù)是包括編碼和單元測(cè)試的。在領(lǐng)任務(wù)的時(shí)候,應(yīng)該跟據(jù)自身的能力,跟組長(zhǎng)或經(jīng)理溝通好,以便留出一定的測(cè)試時(shí)間。當(dāng)然,提高自己的編碼效率也是很有必要的。至于如何提高開(kāi)發(fā)效率,網(wǎng)上有很多這樣的文章,這里就不再贅述了。
重要性
- 提早發(fā)現(xiàn)問(wèn)題
- 增強(qiáng)對(duì)程序的信息
- 理清楚開(kāi)發(fā)邏輯 TDD 測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
- 單元測(cè)試是一個(gè)方法層面上的測(cè)試,也是最細(xì)粒度的測(cè)試。用于測(cè)試一個(gè)類的每一個(gè)方法都已經(jīng)滿足了方法的功能要求。在開(kāi)發(fā)中,對(duì)于自己開(kāi)發(fā)的模塊,只有在通過(guò)單元測(cè)試之后,才能提交到 SVN 庫(kù) 或者 Git 庫(kù)。
單元測(cè)試的形式
TDD
TDD指的是Test Drive Development,很明顯的意思是測(cè)試驅(qū)動(dòng)開(kāi)發(fā),也就是說(shuō)我們可以從測(cè)試的角度來(lái)檢驗(yàn)整個(gè)項(xiàng)目。大概的流程是先針對(duì)每個(gè)功能點(diǎn)抽象出接口代碼,然后編寫單元測(cè)試代碼,接下來(lái)實(shí)現(xiàn)接口,運(yùn)行單元測(cè)試代碼,循環(huán)此過(guò)程,直到整個(gè)單元測(cè)試都通過(guò)。這一點(diǎn)和敏捷開(kāi)發(fā)有類似之處。
TDD的好處自然不用多說(shuō),它能讓你減少程序邏輯方面的錯(cuò)誤,盡可能的減少項(xiàng)目中的bug,開(kāi)始接觸編程的時(shí)候我們大都有過(guò)這樣的體驗(yàn),可能你覺(jué)得完成得很完美,自我感覺(jué)良好,但是實(shí)際測(cè)試或者應(yīng)用的時(shí)候才發(fā)現(xiàn)里面可能存在一堆bug,或者存在設(shè)計(jì)問(wèn)題,或者更嚴(yán)重的邏輯問(wèn)題,而TDD正好可以幫助我們盡量減少類似事件的發(fā)生。而且現(xiàn)在大行其道的一些模式對(duì)TDD的支持都非常不錯(cuò),比如MVC和MVP等。
但是并不是所有的項(xiàng)目都適合TDD這種模式的,我覺(jué)得必須具備以下幾個(gè)條件。
首先,項(xiàng)目的需求必須足夠清晰,而且程序員對(duì)整個(gè)需求有足夠的了解,如果這個(gè)條件不滿足,那么執(zhí)行的過(guò)程中難免失控。當(dāng)然,要達(dá)到這個(gè)目標(biāo)也是需要做一定功課的,這要求我們前期的需求分析以及HLD和LLD都要做得足夠的細(xì)致和完善。
其次,取決于項(xiàng)目的復(fù)雜度和依賴性,對(duì)于一個(gè)業(yè)務(wù)模型及其復(fù)雜、內(nèi)部模塊之間的相互依賴性非常強(qiáng)的項(xiàng)目,采用TDD反而會(huì)得不嘗失,這會(huì)導(dǎo)致程序員在拆分接口和寫測(cè)試代碼的時(shí)候工作量非常大。另外,由于模塊之間的依賴性太強(qiáng),我們?cè)趯憸y(cè)試代碼的時(shí)候可能不采取一些橋接模式來(lái)實(shí)現(xiàn),這樣勢(shì)必加大了程序員的工作量。
BDD
BDD指的是Behavior Drive Development,也就是行為驅(qū)動(dòng)開(kāi)發(fā)。這里的B并非指的是Business,實(shí)際上BDD可以看作是對(duì)TDD的一種補(bǔ)充,當(dāng)然你也可以把它看作TDD的一個(gè)分支。因?yàn)樵赥DD中,我們并不能完全保證根據(jù)設(shè)計(jì)所編寫的測(cè)試就是用戶所期望的功能。BDD將這一部分簡(jiǎn)單和自然化,用自然語(yǔ)言來(lái)描述,讓開(kāi)發(fā)、測(cè)試、BA以及客戶都能在這個(gè)基礎(chǔ)上達(dá)成一致。因?yàn)闇y(cè)試優(yōu)先的概念并不是每個(gè)人都能接受的,可能有人覺(jué)得系統(tǒng)太復(fù)雜而難以測(cè)試,有人認(rèn)為不存在的東西無(wú)法測(cè)試。所以,我們?cè)谶@里試圖轉(zhuǎn)換一種觀念,那便是考慮它的行為,也就是說(shuō)它應(yīng)該如何運(yùn)行,然后抽象出能達(dá)成共識(shí)的規(guī)范。如果你用過(guò)JBehave之類的BDD框架,你將會(huì)更好的理解其中具體的流程。這里我推薦一篇具體闡述的文章。親身體驗(yàn)行為驅(qū)動(dòng)開(kāi)發(fā)。
另外,關(guān)于TDD和BDD之間的關(guān)系,還可以參考這篇文章: 虛擬座談會(huì):代碼測(cè)試比率、測(cè)試驅(qū)動(dòng)開(kāi)發(fā)及行為驅(qū)動(dòng)開(kāi)發(fā)
DDD
DDD指的是Domain Drive Design,也就是領(lǐng)域驅(qū)動(dòng)開(kāi)發(fā)。這是一種非常好的思想,在我們剛開(kāi)始學(xué)習(xí)程序,甚至剛開(kāi)始學(xué)習(xí)三層架構(gòu)的時(shí)候,我們?cè)?jīng)面臨過(guò)很多疑惑,比如如何來(lái)實(shí)現(xiàn)我們的數(shù)據(jù)層?后來(lái)我們開(kāi)始學(xué)習(xí)MVC,MVP等架構(gòu),如何設(shè)計(jì)Model層又成了我們的新問(wèn)題。我們見(jiàn)過(guò)太多這種情況,Model變成了單純的數(shù)據(jù)容器,也就是我們經(jīng)常說(shuō)的貧血模式。DDD實(shí)際上也是建立在這個(gè)基礎(chǔ)之上,因?yàn)樗P(guān)注的是Service層的設(shè)計(jì),著重于業(yè)務(wù)的實(shí)現(xiàn),因此不可避免的以貧血模式為基礎(chǔ)而存在。但是它最大的特別是將分析和設(shè)計(jì)結(jié)合起來(lái),不再使他們處于分裂的狀態(tài),這對(duì)于我們正確完整的實(shí)現(xiàn)客戶的需求,以及建立一個(gè)具有業(yè)務(wù)伸縮性的模型,是有很大幫助的。
nodejs的測(cè)試庫(kù)
原文來(lái)源: