1. 單元測(cè)試的必要性
最近項(xiàng)目有在寫java代碼的單元測(cè)試,然后在思考一個(gè)問(wèn)題,為什么要寫單元測(cè)試??單元測(cè)試寫了有什么用??百度了一圈,如下:
- 軟件質(zhì)量最簡(jiǎn)單、最有效的保證;
- 是目標(biāo)代碼最清晰、最有效的文檔;
- 可以優(yōu)化目標(biāo)代碼的設(shè)計(jì);
- 是代碼重構(gòu)的保障;
- 是回歸測(cè)試和持續(xù)集成的基石。
由于開(kāi)發(fā)經(jīng)驗(yàn)有限,可能說(shuō)的不太對(duì),但是是我目前的個(gè)人的觀點(diǎn),寫單元測(cè)試,有時(shí)候確實(shí)可以發(fā)現(xiàn)bug,+ 但是發(fā)現(xiàn)bug次數(shù)很少,而且目前都是項(xiàng)目開(kāi)發(fā)完了,要上線了,公司有80%的覆蓋率要求,所以都是后期上線之前補(bǔ)。目前而言,并沒(méi)有在很認(rèn)真地寫UT,只是想著完成上線要求。這個(gè)東西吧,也是看成本要求,如果一個(gè)新項(xiàng)目要緊急上線,走緊急發(fā)布特殊流程,單元測(cè)試后期時(shí)間充裕了再補(bǔ)上也行。所以,在時(shí)間允許情況下,我覺(jué)得還是要寫UT,做了有時(shí)候確實(shí)能發(fā)現(xiàn)一些問(wèn)題,尤其對(duì)于一個(gè)大的項(xiàng)目來(lái)說(shuō),一個(gè)bug被隱藏的時(shí)間越長(zhǎng),修復(fù)這個(gè)bug的代價(jià)就越大。在《快速軟件開(kāi)發(fā)》一書中已引用了大量的研究數(shù)據(jù)指出:最后才修改一個(gè) bug 的代價(jià)是在bug產(chǎn)生時(shí)修改它的代價(jià)的10倍。此外,還能學(xué)到一些單元測(cè)試的知識(shí),也算是一種技能上的進(jìn)步吧。
2. Junit4 與 Mock 的介紹
目前應(yīng)用比較普遍的java單元測(cè)試工具 junit4+Mock(Mockito /jmock / powermock)或Stub(用得較少,一般不推薦),由于junit3目前用得不多,基本升級(jí)到j(luò)unit4了,所以就直接簡(jiǎn)單說(shuō)下junit4。
問(wèn)題一:為什么需要mock或stub?它與junit什么關(guān)系?
在做單元測(cè)試的時(shí)候,我們會(huì)發(fā)現(xiàn)我們要測(cè)試的方法會(huì)引用很多外部依賴的對(duì)象,比如:(發(fā)送郵件,網(wǎng)絡(luò)通訊,記錄Log, 文件系統(tǒng) 之類的)。 而我們沒(méi)法控制這些外部依賴的對(duì)象。 為了解決這個(gè)問(wèn)題,我們需要用到Stub和Mock來(lái)模擬這些外部依賴的對(duì)象,從而控制它們。
JUnit是單元測(cè)試框架,可以輕松的完成關(guān)聯(lián)依賴關(guān)系少或者比較簡(jiǎn)單的類的單元測(cè)試,但是對(duì)于關(guān)聯(lián)到其它比較復(fù)雜的類或?qū)\(yùn)行環(huán)境有要求的類的單元測(cè)試,模擬環(huán)境或者配置環(huán)境會(huì)非常耗時(shí),實(shí)施單元測(cè)試比較困難。而這些“mock框架”(Mockito 、jmock 、 powermock、EasyMock),可以通過(guò)mock框架模擬一個(gè)對(duì)象的行為,從而隔離開(kāi)我們不關(guān)心的其他對(duì)象,使得測(cè)試變得簡(jiǎn)單。(例如service調(diào)用dao,即service依賴dao,我們可以通過(guò)mock dao來(lái)模擬真實(shí)的dao調(diào)用,從而能達(dá)到測(cè)試service的目的。)
模擬對(duì)象(Mock Object)可以取代真實(shí)對(duì)象的位置,用于測(cè)試一些與真實(shí)對(duì)象進(jìn)行交互或依賴于真實(shí)對(duì)象的功能,模擬對(duì)象的背后目的就是創(chuàng)建一個(gè)輕量級(jí)的、可控制的對(duì)象來(lái)代替測(cè)試中需要的真實(shí)對(duì)象,模擬真實(shí)對(duì)象的行為和功能。
問(wèn)題二:mock與stub什么區(qū)別?
Mock和Stub是兩種測(cè)試代碼功能的方法。Mock測(cè)重于對(duì)功能的模擬,Stub測(cè)重于對(duì)功能的測(cè)試重現(xiàn)。比如對(duì)于List接口,Mock會(huì)直接對(duì)List進(jìn)行模擬,而Stub會(huì)新建一個(gè)實(shí)現(xiàn)了List的TestList,在其中編寫測(cè)試的代碼。
強(qiáng)烈建議優(yōu)先選擇Mock方式,因?yàn)镸ock方式下,模擬代碼與測(cè)試代碼放在一起,易讀性好,而且擴(kuò)展性、靈活性都比Stub好。
其中EasyMock和Mockito對(duì)于Java接口使用接口代理的方式來(lái)模擬,對(duì)于Java類使用繼承的方式來(lái)模擬(也即會(huì)創(chuàng)建一個(gè)新的Class類)。Mockito支持spy方式,可以對(duì)實(shí)例進(jìn)行模擬。但它們都不能對(duì)靜態(tài)方法和final類進(jìn)行模擬,powermock通過(guò)修改字節(jié)碼來(lái)支持了此功能。
有篇文章介紹:http://blog.csdn.net/devhubs/article/details/8018084
二、junit4相關(guān)介紹
這里有篇文章介紹了junit4的一些,包括怎么引入,使用,蠻詳細(xì)。---》 http://blog.csdn.net/happylee6688/article/details/38069761
這邊就記錄一些常用注解,當(dāng)做學(xué)習(xí)方便。
常用注解
@Before:初始化方法,在任何一個(gè)測(cè)試方法執(zhí)行之前,必須執(zhí)行的代碼。對(duì)比 JUnit 3 ,和 setUp()方法具有相同的功能。在該注解的方法中,可以進(jìn)行一些準(zhǔn)備工作,比如初始化對(duì)象,打開(kāi)網(wǎng)絡(luò)連接等。
@After:釋放資源,在任何一個(gè)測(cè)試方法執(zhí)行之后,需要進(jìn)行的收尾工作。對(duì)比 JUnit 3 ,和 tearDown()方法具有相同的功能。
@Test:測(cè)試方法,表明這是一個(gè)測(cè)試方法。在 JUnit 中將會(huì)自動(dòng)被執(zhí)行。對(duì)與方法的聲明也有如下要求:名字可以隨便取,沒(méi)有任何限制,但是返回值必須為 void ,而且不能有任何參數(shù)。如果違反這些規(guī)定,會(huì)在運(yùn)行時(shí)拋出一個(gè)異常。不過(guò),為了培養(yǎng)一個(gè)好的編程習(xí)慣,我們一般在測(cè)試的方法名上加 test ,比如:testAdd()。
同時(shí),該 Annotation(@Test) 還可以測(cè)試期望異常和超時(shí)時(shí)間,如 @Test(timeout=100),我們給測(cè)試函數(shù)設(shè)定一個(gè)執(zhí)行時(shí)間,超過(guò)這個(gè)時(shí)間(100毫秒),他們就會(huì)被系統(tǒng)強(qiáng)行終止,并且系統(tǒng)還會(huì)向你匯報(bào)該函數(shù)結(jié)束的原因是因?yàn)槌瑫r(shí),這樣你就可以發(fā)現(xiàn)這些 bug 了。而且,它還可以測(cè)試期望的異常,例如,我們剛剛的那個(gè)空指針異常就可以這樣:@Test(expected=NullPointerException.class)。
@Ignore:忽略的測(cè)試方法,標(biāo)注的含義就是“某些方法尚未完成,咱不參與此次測(cè)試”;這樣的話測(cè)試結(jié)果就會(huì)提示你有幾個(gè)測(cè)試被忽略,而不是失敗。一旦你完成了相應(yīng)的函數(shù),只需要把 @Ignore 注解刪除即可,就可以進(jìn)行正常測(cè)試了。當(dāng)然,這個(gè) @Ignore 注解對(duì)于像我這樣有“強(qiáng)迫癥”的人還是大有意義的。每當(dāng)看到紅色條(測(cè)試失敗)的時(shí)候就會(huì)全身不舒服,感覺(jué)無(wú)法忍受(除非要測(cè)試的目的就是讓它失敗)。當(dāng)然,對(duì)代碼也是一樣,無(wú)法忍受那些雜亂不堪的代碼。
@BeforeClass:針對(duì)所有測(cè)試,也就是整個(gè)測(cè)試類中,在所有測(cè)試方法執(zhí)行前,都會(huì)先執(zhí)行由它注解的方法,而且只執(zhí)行一次。當(dāng)然,需要注意的是,修飾符必須是 public static void xxxx ;此 Annotation 是 JUnit 4 新增的功能。
@AfterClass:針對(duì)所有測(cè)試,也就是整個(gè)測(cè)試類中,在所有測(cè)試方法都執(zhí)行完之后,才會(huì)執(zhí)行由它注解的方法,而且只執(zhí)行一次。當(dāng)然,需要注意的是,修飾符也必須是 public static void xxxx ;此 Annotation 也是 JUnit 4 新增的功能,與 @BeforeClass 是一對(duì)。
執(zhí)行順序
所以,在 JUnit 4 中,單元測(cè)試用例的執(zhí)行順序?yàn)椋?/p>
三、Mock的幾種比較(Mockito 、jmock 、 powermock)
介紹文章一:http://blog.csdn.net/luvinahlc/article/details/10442743
介紹文章二:http://blog.csdn.net/zhangxin09/article/details/42422643
介紹文章三(Mockito 文檔):https://static.javadoc.io/org.mockito/mockito-core/2.8.47/org/mockito/Mockito.html
Spring提供了對(duì)Junit支持,可以使用注解的方式(注解加在需要測(cè)試的類上):
@RunWIth(SpringJunit4ClassRunner.class) ---->為了讓測(cè)試在Spring容器環(huán)境下執(zhí)行
@ContextConfiguration(locations = {"classpath:applicationContext.xml"} --->用來(lái)指明Spring的配置文件位置
Mockito簡(jiǎn)單運(yùn)用說(shuō)明
① when(mock.someMethod()).thenReturn(value):設(shè)定mock對(duì)象某個(gè)方法調(diào)用時(shí)的返回值。可以連續(xù)設(shè)定返回值,即when(mock.someMethod()).thenReturn(value1).then
Return(value2),第一次調(diào)用時(shí)返回value1,第二次返回value2。也可以表示為如下:
when(mock.someMethod()).thenReturn(value1,value2)。
② 調(diào)用以上方法時(shí)拋出異常: when(mock.someMethod()).thenThrow(new Runtime
Exception());
③ 另一種stubbing語(yǔ)法:
doReturn(value).when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
④ 對(duì)void方法進(jìn)行方法預(yù)期設(shè)定只能用如下語(yǔ)法:
doNothing().when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
doNothing().doThrow(new RuntimeException()).when(mock.someMethod())
⑤ 方法的參數(shù)可以使用參數(shù)模擬器,可以將anyInt()傳入任何參數(shù)為int的方法,即anyInt匹配任何int類型的參數(shù),anyString()匹配任何字符串,anySet()匹配任何Set。
⑥ Mock對(duì)象只能調(diào)用stubbed方法,調(diào)用不了它真實(shí)的方法,但是Mockito可以用spy來(lái)監(jiān)控一個(gè)真實(shí)對(duì)象,這樣既可以stubbing這個(gè)對(duì)象的方法讓它返回我們的期望值,又可以使得對(duì)其他方法調(diào)用時(shí)將會(huì)調(diào)用它的真實(shí)方法。
⑦ Mockito會(huì)自動(dòng)記錄自己的交互行為,可以用verify(…).methodXxx(…)語(yǔ)法來(lái)驗(yàn)證方法Xxx是否按照預(yù)期進(jìn)行了調(diào)用。
(1) 驗(yàn)證調(diào)用次數(shù):verify(mock,times(n)).someMethod(argument),n為被調(diào)用的次數(shù),如果超過(guò)或少于n都算失敗。除了times(n),還有never(),atLease(n),atMost(n)。
(2) 驗(yàn)證超時(shí):verify(mock, timeout(100)).someMethod();
(3) 同時(shí)驗(yàn)證:verify(mock, timeout(100).times(1)).someMethod();
相關(guān)注解:
MockitoAnnotations.initMocks(this);
initializes fields annotated with Mockito annotations.
Allows shorthand creation of objects required for testing.
Minimizes repetitive mock creation code.
Makes the test class more readable.
Makes the verification error easier to read because field name is used to identify the mock.
ReflectionTestUtils.setField(AopTargetUtils.getTarget(appInfoService), "openAppInfoMapper",openAppInfoMapperMock);
但是由于Spring可以使用@Autoware類似的注解方式,對(duì)私有的成員進(jìn)行賦值,此時(shí)無(wú)法直接對(duì)私有的依賴設(shè)置mock對(duì)象。可以通過(guò)引入ReflectionTestUtils,解決依賴注入的問(wèn)題。
(不是很理解。。。。,因?yàn)槲覍?duì)某個(gè)service的private dao,直接mock,并沒(méi)有設(shè)置ReflectionTestUtils.setField(),照樣可以運(yùn)行ok,那么這個(gè)什么時(shí)候用到?。)
@InjectMocks --- injects mock or spy fields into tested object automatically.
這個(gè)注解不會(huì)把一個(gè)類變成mock或是spy,但是會(huì)把當(dāng)前對(duì)象下面的Mock/Spy類注入進(jìn)去,按類型注入。
@Mock 生成的類,所有方法都不是真實(shí)的方法,而且返回值都是NULL。---> when(dao.getOrder()).thenReturn("returened by mock ");
@Spy ---Creates a spy of the real object. The spy calls real methods unless they are stubbed.
生成的類,所有方法都是真實(shí)方法,返回值都是和真實(shí)方法一樣的。---> doReturn("twotwo").when(ps).getPriceTwo();
Mockito可以完成對(duì)一般對(duì)象方法的模擬,但是對(duì)于靜態(tài)函數(shù)、構(gòu)造函數(shù)、私有函數(shù)等還是無(wú)能為力.