單元測(cè)試到底應(yīng)該測(cè)試什么呢?
開(kāi)始
現(xiàn)在大公司越來(lái)越重視項(xiàng)目的單元測(cè)試,甚至明確要求項(xiàng)目的單元測(cè)試覆蓋率不能低于某個(gè)值,足可見(jiàn)單元測(cè)試的重要性;
試想如果沒(méi)有單元測(cè)試,那么如何保證代碼能夠正常運(yùn)行呢?
測(cè)試人員做的只是業(yè)務(wù)上的集成測(cè)試,也就是黑盒測(cè)試,對(duì)單個(gè)的方法是沒(méi)有辦法測(cè)試的,而且,測(cè)試出的 bug 的范圍也會(huì)很廣,根本不能確定 bug 的范圍,還得去花時(shí)間來(lái)確定 bug 出在什么地方。
另外,一個(gè)最常見(jiàn)的問(wèn)題:寫單測(cè)浪費(fèi)時(shí)間??你有沒(méi)有計(jì)算過(guò)你改bug的時(shí)間(定位+修復(fù)),算一下的話你會(huì)發(fā)現(xiàn)時(shí)間浪費(fèi)的會(huì)更多。
參考建議
關(guān)于如何寫好單元測(cè)試,下面有幾條建議供大家參考:
1. 測(cè)試數(shù)據(jù)外部化
測(cè)試數(shù)據(jù)大致分為兩種:變化的和不變化的,對(duì)于不變的測(cè)試數(shù)據(jù),我們完全可以寫在單元測(cè)試用例代碼中,也可以將數(shù)據(jù)外部化。
而對(duì)于測(cè)試數(shù)據(jù)一直在變,并且測(cè)試數(shù)據(jù)量比較大的時(shí)候可以使用測(cè)試數(shù)據(jù)外部化將數(shù)據(jù)放在測(cè)試用例的外部進(jìn)行統(tǒng)一管理。
什么是數(shù)據(jù)外部化?就是將數(shù)據(jù)放在單元測(cè)試用例的外部統(tǒng)一管理,比如我們可以將一個(gè)單元測(cè)試用例中的測(cè)試數(shù)據(jù)統(tǒng)一放在一個(gè)CSV文件中。我們就可以通過(guò)比如junit5中的參數(shù)測(cè)試注解 @ParameterizedTest和引入CVS文件的注解@CsvFileSource并指定其中的resources屬性指定CSV文件,
numLinesToSkip = n 屬性指定從第n+1行開(kāi)始。這樣就可以通過(guò)一個(gè)CSV文件統(tǒng)一管理一個(gè)單元測(cè)試用例中的數(shù)據(jù)。我們管理測(cè)試用例中所需要的數(shù)據(jù)就只需要管理一個(gè)個(gè)CSV文件即可。
下面可以看一個(gè)案例:
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSource(String first, int second) {
assertNotNull(first);
assertNotEquals(0, second);
}
其中,two-column.csv文件內(nèi)容
Country, reference
Sweden, 1
Poland, 2
"United States of America", 3
2. 構(gòu)建具有特定結(jié)果的測(cè)試
如果方法結(jié)果具有隨機(jī)性,這樣的方法幾乎無(wú)法測(cè)試,所以我們針對(duì)這種方法便沒(méi)有辦法去進(jìn)行測(cè)試。
我們只能對(duì)根據(jù)特有數(shù)據(jù)得到特定結(jié)果的方法進(jìn)行測(cè)試。
3. 測(cè)試方面全面,設(shè)計(jì)的每一方面必須有一個(gè)測(cè)試用例:
正面所有情景
負(fù)面所有情景
臨界值
特殊值
4. 測(cè)試用例請(qǐng)盡量簡(jiǎn)潔、簡(jiǎn)短
在能完成測(cè)試的基礎(chǔ)上盡量簡(jiǎn)潔代碼,這樣不僅使代碼更加好看,還好維護(hù)好理解。想想一大堆代碼和幾行代碼你更想看哪個(gè)?
5. 測(cè)試用例盡量快
對(duì)于單元測(cè)試用例我們幾乎每開(kāi)發(fā)完一個(gè)方法或者修改完一個(gè)方法,我們幾乎都會(huì)去運(yùn)行一遍測(cè)試用例,確保沒(méi)有影響到其他模塊的正常運(yùn)行,所以我們要盡量讓你的測(cè)試方法“快!”,移除一些和單元測(cè)試無(wú)關(guān)的代碼。
當(dāng)然,前提還是要保證測(cè)試的完整性與正確性。
6. 每次運(yùn)行單元測(cè)試時(shí),請(qǐng)確保100%運(yùn)行成功!
這個(gè)相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,但是做起來(lái)是比較難的,因?yàn)榭赡軙?huì)有多種原因?qū)е履愕臏y(cè)試用例失敗,比如:數(shù)據(jù)過(guò)期、方法內(nèi)部邏輯改變等。推薦閱讀:Spring Boot 單元測(cè)試詳解+實(shí)戰(zhàn)教程。
這些可能會(huì)花費(fèi)你的一些時(shí)間去修改,你往往可能不愿意,不過(guò)既然做了一件事,就做好一件事唄
但是如果你不注意這些小錯(cuò)誤,這可能就會(huì)導(dǎo)致你的一個(gè)大流程失敗,大家應(yīng)該知道,我們?cè)谶\(yùn)行一個(gè)流程時(shí)往往一個(gè)小小的錯(cuò)誤就導(dǎo)致流程整理失?。?/p>
7. 設(shè)計(jì)好你的測(cè)試
這包含的方面就比較廣了,下面幾個(gè)方面我認(rèn)為大家應(yīng)該注意的:
前面所說(shuō)的代碼在保證質(zhì)量的前提下盡量簡(jiǎn)潔
單元測(cè)試中代碼的抽象也是可以有的,我們也可以將一些可重用的代碼抽象出來(lái),提高代碼的重用性和減少代碼的重復(fù)。
給測(cè)試類測(cè)試方法起一個(gè)好名字。測(cè)試類一般是“類名+Test后綴”,可以表示對(duì)哪個(gè)類進(jìn)行的測(cè)試。測(cè)試方法也是類似,“測(cè)試方法名+Test后綴”或者對(duì)一個(gè)方法的部分測(cè)試“測(cè)試方法名+測(cè)試部分作用+Test后綴”。
每個(gè)測(cè)試方法對(duì)被測(cè)試方法的功能斷言不宜過(guò)多,如果一個(gè)方法需要多個(gè)斷言進(jìn)行測(cè)試,我們可以進(jìn)行大致分類,將其分布到兩個(gè)測(cè)試方法中,這樣可以細(xì)粒度的進(jìn)行測(cè)試。
8. 注意測(cè)試代碼覆蓋率
一個(gè)設(shè)計(jì)好的單元測(cè)試,其代碼測(cè)試覆蓋率也是很高的,并不要求100% 的測(cè)試代碼覆蓋率,但是高覆蓋率的代碼包含未檢測(cè)到的錯(cuò)誤的幾率要低,因?yàn)槠涓嗟脑创a在測(cè)試過(guò)程中被執(zhí)行。
注意:高代碼覆蓋不能保證測(cè)試是完美的,所以要小心!
9. 還有就是一些其他的注意點(diǎn)了,比如
不要使用print語(yǔ)句去輸出測(cè)試結(jié)果人工判斷是否正確,要使用斷言
一些不好理解的測(cè)試最好在方法上面寫明注釋,便于后期理解與維護(hù)
使用框架進(jìn)行單元測(cè)試,比如Junit5如果其中的斷言支持不滿足你的需求也可以使用ASsertJ框架來(lái)豐富斷言,Mockito進(jìn)行Mock數(shù)據(jù)等
好了,上述就是對(duì)如何寫好單元測(cè)試的一些建議,如有不當(dāng),請(qǐng)?jiān)谠u(píng)論區(qū)中指出,感激不盡!
點(diǎn)擊「閱讀原文」獲取面試題大全~
