【轉(zhuǎn)】關(guān)于java 單元測試Junit4和Mock的一些總結(jié)

原文出處請點擊這里

1. 單元測試的必要性

最近項目有在寫java代碼的單元測試,然后在思考一個問題,為什么要寫單元測試??單元測試寫了有什么用??百度了一圈,如下:

  • 軟件質(zhì)量最簡單、最有效的保證;
  • 是目標(biāo)代碼最清晰、最有效的文檔;
  • 可以優(yōu)化目標(biāo)代碼的設(shè)計;
  • 是代碼重構(gòu)的保障;
  • 是回歸測試和持續(xù)集成的基石。

由于開發(fā)經(jīng)驗有限,可能說的不太對,但是是我目前的個人的觀點,寫單元測試,有時候確實可以發(fā)現(xiàn)bug,+ 但是發(fā)現(xiàn)bug次數(shù)很少,而且目前都是項目開發(fā)完了,要上線了,公司有80%的覆蓋率要求,所以都是后期上線之前補(bǔ)。目前而言,并沒有在很認(rèn)真地寫UT,只是想著完成上線要求。這個東西吧,也是看成本要求,如果一個新項目要緊急上線,走緊急發(fā)布特殊流程,單元測試后期時間充裕了再補(bǔ)上也行。所以,在時間允許情況下,我覺得還是要寫UT,做了有時候確實能發(fā)現(xiàn)一些問題,尤其對于一個大的項目來說,一個bug被隱藏的時間越長,修復(fù)這個bug的代價就越大。在《快速軟件開發(fā)》一書中已引用了大量的研究數(shù)據(jù)指出:最后才修改一個 bug 的代價是在bug產(chǎn)生時修改它的代價的10倍。此外,還能學(xué)到一些單元測試的知識,也算是一種技能上的進(jìn)步吧。

2. Junit4 與 Mock 的介紹

目前應(yīng)用比較普遍的java單元測試工具 junit4+Mock(Mockito /jmock / powermock)或Stub(用得較少,一般不推薦),由于junit3目前用得不多,基本升級到j(luò)unit4了,所以就直接簡單說下junit4。

問題一:為什么需要mock或stub?它與junit什么關(guān)系?

在做單元測試的時候,我們會發(fā)現(xiàn)我們要測試的方法會引用很多外部依賴的對象,比如:(發(fā)送郵件,網(wǎng)絡(luò)通訊,記錄Log, 文件系統(tǒng) 之類的)。 而我們沒法控制這些外部依賴的對象。 為了解決這個問題,我們需要用到Stub和Mock來模擬這些外部依賴的對象,從而控制它們。

JUnit是單元測試框架,可以輕松的完成關(guān)聯(lián)依賴關(guān)系少或者比較簡單的類的單元測試,但是對于關(guān)聯(lián)到其它比較復(fù)雜的類或?qū)\(yùn)行環(huán)境有要求的類的單元測試,模擬環(huán)境或者配置環(huán)境會非常耗時,實施單元測試比較困難。而這些“mock框架”(Mockito 、jmock 、 powermock、EasyMock),可以通過mock框架模擬一個對象的行為,從而隔離開我們不關(guān)心的其他對象,使得測試變得簡單。(例如service調(diào)用dao,即service依賴dao,我們可以通過mock dao來模擬真實的dao調(diào)用,從而能達(dá)到測試service的目的。)

模擬對象(Mock Object)可以取代真實對象的位置,用于測試一些與真實對象進(jìn)行交互或依賴于真實對象的功能,模擬對象的背后目的就是創(chuàng)建一個輕量級的、可控制的對象來代替測試中需要的真實對象,模擬真實對象的行為和功能。

問題二:mock與stub什么區(qū)別?

Mock和Stub是兩種測試代碼功能的方法。Mock測重于對功能的模擬,Stub測重于對功能的測試重現(xiàn)。比如對于List接口,Mock會直接對List進(jìn)行模擬,而Stub會新建一個實現(xiàn)了List的TestList,在其中編寫測試的代碼。
強(qiáng)烈建議優(yōu)先選擇Mock方式,因為Mock方式下,模擬代碼與測試代碼放在一起,易讀性好,而且擴(kuò)展性、靈活性都比Stub好。

其中EasyMock和Mockito對于Java接口使用接口代理的方式來模擬,對于Java類使用繼承的方式來模擬(也即會創(chuàng)建一個新的Class類)。Mockito支持spy方式,可以對實例進(jìn)行模擬。但它們都不能對靜態(tài)方法和final類進(jìn)行模擬,powermock通過修改字節(jié)碼來支持了此功能。

有篇文章介紹: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:初始化方法,在任何一個測試方法執(zhí)行之前,必須執(zhí)行的代碼。對比 JUnit 3 ,和 setUp()方法具有相同的功能。在該注解的方法中,可以進(jìn)行一些準(zhǔn)備工作,比如初始化對象,打開網(wǎng)絡(luò)連接等。

@After:釋放資源,在任何一個測試方法執(zhí)行之后,需要進(jìn)行的收尾工作。對比 JUnit 3 ,和 tearDown()方法具有相同的功能。

@Test:測試方法,表明這是一個測試方法。在 JUnit 中將會自動被執(zhí)行。對與方法的聲明也有如下要求:名字可以隨便取,沒有任何限制,但是返回值必須為 void ,而且不能有任何參數(shù)。如果違反這些規(guī)定,會在運(yùn)行時拋出一個異常。不過,為了培養(yǎng)一個好的編程習(xí)慣,我們一般在測試的方法名上加 test ,比如:testAdd()。
同時,該 Annotation(@Test) 還可以測試期望異常和超時時間,如 @Test(timeout=100),我們給測試函數(shù)設(shè)定一個執(zhí)行時間,超過這個時間(100毫秒),他們就會被系統(tǒng)強(qiáng)行終止,并且系統(tǒng)還會向你匯報該函數(shù)結(jié)束的原因是因為超時,這樣你就可以發(fā)現(xiàn)這些 bug 了。而且,它還可以測試期望的異常,例如,我們剛剛的那個空指針異常就可以這樣:@Test(expected=NullPointerException.class)。

@Ignore:忽略的測試方法,標(biāo)注的含義就是“某些方法尚未完成,咱不參與此次測試”;這樣的話測試結(jié)果就會提示你有幾個測試被忽略,而不是失敗。一旦你完成了相應(yīng)的函數(shù),只需要把 @Ignore 注解刪除即可,就可以進(jìn)行正常測試了。當(dāng)然,這個 @Ignore 注解對于像我這樣有“強(qiáng)迫癥”的人還是大有意義的。每當(dāng)看到紅色條(測試失?。┑臅r候就會全身不舒服,感覺無法忍受(除非要測試的目的就是讓它失?。.?dāng)然,對代碼也是一樣,無法忍受那些雜亂不堪的代碼。

@BeforeClass:針對所有測試,也就是整個測試類中,在所有測試方法執(zhí)行前,都會先執(zhí)行由它注解的方法,而且只執(zhí)行一次。當(dāng)然,需要注意的是,修飾符必須是 public static void xxxx ;此 Annotation 是 JUnit 4 新增的功能。

@AfterClass:針對所有測試,也就是整個測試類中,在所有測試方法都執(zhí)行完之后,才會執(zhí)行由它注解的方法,而且只執(zhí)行一次。當(dāng)然,需要注意的是,修飾符也必須是 public static void xxxx ;此 Annotation 也是 JUnit 4 新增的功能,與 @BeforeClass 是一對。

執(zhí)行順序
所以,在 JUnit 4 中,單元測試用例的執(zhí)行順序為:

三、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提供了對Junit支持,可以使用注解的方式(注解加在需要測試的類上):

@RunWIth(SpringJunit4ClassRunner.class) ---->為了讓測試在Spring容器環(huán)境下執(zhí)行

@ContextConfiguration(locations = {"classpath:applicationContext.xml"} --->用來指明Spring的配置文件位置

Mockito簡單運(yùn)用說明

① when(mock.someMethod()).thenReturn(value):設(shè)定mock對象某個方法調(diào)用時的返回值??梢赃B續(xù)設(shè)定返回值,即when(mock.someMethod()).thenReturn(value1).then
Return(value2),第一次調(diào)用時返回value1,第二次返回value2。也可以表示為如下:
when(mock.someMethod()).thenReturn(value1,value2)。
② 調(diào)用以上方法時拋出異常: when(mock.someMethod()).thenThrow(new Runtime
Exception());
③ 另一種stubbing語法:
doReturn(value).when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
④ 對void方法進(jìn)行方法預(yù)期設(shè)定只能用如下語法:
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對象只能調(diào)用stubbed方法,調(diào)用不了它真實的方法,但是Mockito可以用spy來監(jiān)控一個真實對象,這樣既可以stubbing這個對象的方法讓它返回我們的期望值,又可以使得對其他方法調(diào)用時將會調(diào)用它的真實方法。
⑦ Mockito會自動記錄自己的交互行為,可以用verify(…).methodXxx(…)語法來驗證方法Xxx是否按照預(yù)期進(jìn)行了調(diào)用。
(1) 驗證調(diào)用次數(shù):verify(mock,times(n)).someMethod(argument),n為被調(diào)用的次數(shù),如果超過或少于n都算失敗。除了times(n),還有never(),atLease(n),atMost(n)。
(2) 驗證超時:verify(mock, timeout(100)).someMethod();
(3) 同時驗證: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類似的注解方式,對私有的成員進(jìn)行賦值,此時無法直接對私有的依賴設(shè)置mock對象。可以通過引入ReflectionTestUtils,解決依賴注入的問題。

(不是很理解。。。。,因為我對某個service的private dao,直接mock,并沒有設(shè)置ReflectionTestUtils.setField(),照樣可以運(yùn)行ok,那么這個什么時候用到?。)

@InjectMocks --- injects mock or spy fields into tested object automatically.

這個注解不會把一個類變成mock或是spy,但是會把當(dāng)前對象下面的Mock/Spy類注入進(jìn)去,按類型注入。

@Mock 生成的類,所有方法都不是真實的方法,而且返回值都是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.

生成的類,所有方法都是真實方法,返回值都是和真實方法一樣的。---> doReturn("twotwo").when(ps).getPriceTwo();

Mockito可以完成對一般對象方法的模擬,但是對于靜態(tài)函數(shù)、構(gòu)造函數(shù)、私有函數(shù)等還是無能為力.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容