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

原文出處請(qǐng)點(diǎn)擊這里

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ú)能為力.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,730評(píng)論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,118評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,873評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,266評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,482評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,846評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,025評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,279評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,684評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,953評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,751評(píng)論 3 394
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,016評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容