這篇文章主要講解了Java中UnitTest和PowerMock的詳細(xì)解析,內(nèi)容清晰明了,對(duì)此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會(huì)有幫助。
成都創(chuàng)新互聯(lián)主要從事成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)壺關(guān),十年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18980820575
學(xué)習(xí)一門計(jì)算機(jī)語言,我覺得除了學(xué)習(xí)它的語法外,最重要的就是要學(xué)習(xí)怎么在這個(gè)語言環(huán)境下進(jìn)行單元測(cè)試,因?yàn)閱卧獪y(cè)試能幫你提早發(fā)現(xiàn)錯(cuò)誤;同時(shí)給你的程序加一道防護(hù)網(wǎng),防止你的修改破壞了原有的功能;單元測(cè)試還能指引你寫出更好的代碼,畢竟不能被測(cè)試的代碼一定不是好代碼;除此之外,它還能增加你的自信,能勇敢的說出「我的程序沒有bug」。
每個(gè)語言都有其常用的單元測(cè)試框架,本文主要介紹在 Java 中,我們?nèi)绾问褂?PowerMock,來解決我們?cè)趯憜卧獪y(cè)試時(shí)遇到的問題,從 Mock 這個(gè)詞可以看出,這類問題主要是解依賴問題。
在寫單元測(cè)試時(shí),為了讓測(cè)試工作更簡(jiǎn)單、減少外部的不確定性,我們一般都會(huì)把被測(cè)類和其他依賴類進(jìn)行隔離,不然你的類依賴得越多,你需要做的準(zhǔn)備工作就越復(fù)雜,尤其是當(dāng)它依賴網(wǎng)絡(luò)或外部數(shù)據(jù)庫時(shí),會(huì)給測(cè)試帶來極大的不確定性,而我們的單測(cè)一定要滿足快速、可重復(fù)執(zhí)行的要求,所以隔離或解依賴是必不可少的步驟。
而 Java 中的 PowerMock 庫是一個(gè)非常強(qiáng)大的解依賴庫,下面談到的 3 個(gè)特性,可以幫你解決絕大多數(shù)問題:
1 通過 PowerMock 注入依賴對(duì)象
2 利用 PowerMock 來 mock static 函數(shù)
3 輸出參數(shù)(output parameter)怎么 mock
通過 PowerMock 注入依賴對(duì)象
假設(shè)你有兩個(gè)類,MyService
和 MyDao
,MyService
依賴于 MyDao
,且它們的定義如下
// MyDao.java @Mapper public interface MyDao { /** * 根據(jù)用戶 id 查看他最近一次操作的時(shí)間 */ Date getLastOperationTime(long userId); } // MyService.java @Service public class MyService { @Autowired private MyDao myDao; public boolean operate(long userId, String operation) { Date lastTime = myDao.getLastOperationTime(userId); // ... } }
這個(gè)服務(wù)提供一個(gè) operate
接口,用戶在調(diào)用該接口時(shí),會(huì)被限制一個(gè)操作頻次,所以系統(tǒng)會(huì)記錄每個(gè)用戶上次操作的時(shí)間,通過 MyDao.getLastOperationTime(long userId)
接口獲取,現(xiàn)在我們要對(duì) MyService
類的 operate
做單元測(cè)試,該怎么做?
你可能會(huì)想到使用 SpringBoot,它能自動(dòng)幫我們初始化 myDao
對(duì)象,但這樣做卻存在一些問題:
1 SpringBoot 的啟動(dòng)速度很慢,這會(huì)延長(zhǎng)單元測(cè)試的時(shí)間
2 因?yàn)闀r(shí)間是一個(gè)不斷變化的量,也許這一次你構(gòu)造的時(shí)間滿足測(cè)試條件,但下一次運(yùn)行測(cè)試時(shí),可能就不滿足了。
由于以上原因,我們一般在做單元測(cè)試時(shí),不啟動(dòng) SpringBoot 上下文,而是采用 PowerMock 幫我們注入依賴,對(duì)于上面的 case,我們的測(cè)試用例可以這樣寫:
// MyServiceTest.java @RunWith(PowerMockRunner.class) @PrepareForTest({MyService.class, MyDao.class}) public class MyServiceTest { @Test public void testOperate() throws IllegalAccessException { // 構(gòu)造一個(gè)和當(dāng)前調(diào)用時(shí)間永遠(yuǎn)只差 4 秒的返回值 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.SECOND, -4); Date retTime = calendar.getTime(); // spy 是對(duì)象的“部分 mock” MyService myService = PowerMockito.spy(new MyService()); MyDao md = PowerMockito.mock(MyDao.class); PowerMockito .when(md.getLastOperationTime(Mockito.any(long.class))) .thenReturn(retTime); // 替換 myDao 成員 MemberModifier.field(MyService.class, "myDao").set(myService, md); // 假設(shè)最小操作的間隔是 5 秒,否則返回 false Assert.assertFalse(myService.operate(1, "test operation")); } }
從上面代碼中,我們首先構(gòu)造了一個(gè)返回時(shí)間 retTime
,模擬操作間隔的時(shí)間為 4 秒,保證了每次運(yùn)行測(cè)試時(shí)該條件不會(huì)變化;然后我們用 spy
構(gòu)造一個(gè)待測(cè)試的 MyService
對(duì)象,spy
和 mock
的區(qū)別是,spy
只會(huì)部分模擬對(duì)象,即這里只修改掉 myService.myDao 成
員,其他的保持不變。
然后我們定義了被 mock 的對(duì)象 MyDao md
的調(diào)用行為,當(dāng) md.getLastOperationTime
函數(shù)被調(diào)用時(shí),返回我們構(gòu)造的時(shí)間 retTime
,此時(shí)測(cè)試環(huán)境就設(shè)置完畢了,這樣做之后,你就可以很容易的測(cè)試 operate
函數(shù)了。
利用 PowerMock 來 mock static 函數(shù)
上文所說的使用 PowerMock 進(jìn)行依賴注入,可以覆蓋測(cè)試中絕大多數(shù)的解依賴場(chǎng)景,而另一種常見的依賴是 static 函數(shù),例如我們自己寫的一些 CommonUtil
工具類中的函數(shù)。
還是使用上面的例子,假設(shè)我們要計(jì)算當(dāng)前時(shí)間和用戶上一次操作時(shí)間之間的間隔,并使用 public static long getTimeInterval(Date lastTime)
實(shí)現(xiàn)該功能,如下:
// CommonUtil.java class CommonUtil { public static long getTimeInterval(Date lastTime) { long duration = Duration.between(lastTime.toInstant(), new Date().toInstant()).getSeconds(); return duration; } }
我們的 operator
函數(shù)修改如下
// MyService.java // ... public boolean operate(long userId, String operation) { Date lastTime = myDao.getLastOperationTime(userId); long duration = CommonUtil.getTimeInterval(lastTime); if (duration >= 5) { System.out.println("user: " + userId + " " + operation); return true; } else { return false; } } // ...
這里先從 myDao
獲取上次操作的時(shí)間,再調(diào)用 CommonUtil.getTimeInterval
計(jì)算操作間隔,如果小于 5 秒,就返回 false
,否則執(zhí)行操作,并返回 true
。那么我的問題是,如何解掉這里 static 函數(shù)的依賴呢?我們直接看測(cè)試代碼吧
// MyServiceTest.java @PrepareForTest({MyService.class, MyDao.class, CommonUtil.class}) public class MyServiceTest { // ... @Test public void testOperateWithStatic() throws IllegalAccessException { // ... PowerMockito.spy(CommonUtil.class); PowerMockito.doReturn(5L).when(CommonUtil.class); CommonUtil.getTimeInterval(Mockito.anyObject()); // ... } }
首先在注解 @PrepareForTest 中增加 CommonUtil.class,依然使用 spy 對(duì)類 CommonUtil 進(jìn)行 mock,如果不這么做,這個(gè)類中所有靜態(tài)函數(shù)的行為都會(huì)發(fā)生變化,這會(huì)給你的測(cè)試帶來麻煩。spy 下面的兩行代碼你應(yīng)該放在一起解讀,意為當(dāng)調(diào)用 CommonUtil.getTimeInterval 時(shí),返回 5;這種寫法比較奇怪,但卻是 PowerMock 要求的。至此,你已經(jīng)掌握了 mock static 函數(shù)的技巧。
輸出參數(shù)(output parameter)怎么 mock
有些函數(shù)會(huì)通過修改參數(shù)所引用的對(duì)象作為輸出,例如下面的這個(gè)場(chǎng)景,假設(shè)我們的 operation 是一個(gè)長(zhǎng)時(shí)間執(zhí)行的任務(wù),我們需要不斷輪訓(xùn)該任務(wù)的狀態(tài),更新到內(nèi)存,并對(duì)外提供查詢接口,如下代碼:到內(nèi)存,并對(duì)外提供查詢接口,如下代碼:
// MyTask.java // ... public boolean run() throws InterruptedException { while (true) { updateStatus(operation); if (operation.getStatus().equals("success")) { return true; } else { Thread.sleep(1000); } } } public void updateStatus(Operation operation) { String status = myDao.getStatus(operation.getOperationId()); operation.setStatus(status); } // ...
上面的代碼中,run() 是一個(gè)輪詢?nèi)蝿?wù),它會(huì)不斷更新 operation 的狀態(tài),并在狀態(tài)達(dá)到 "success" 時(shí)停止,可以看到,updateStatus 就是我們所說的函數(shù),雖然它沒有返回值,但它會(huì)修改參數(shù)所引用的對(duì)象,所以這種參數(shù)也被稱作輸出參數(shù)。
現(xiàn)在我們要測(cè)試 run() 函數(shù)的行為,看它是否會(huì)在 "success" 狀態(tài)下退出,那么我們就需要 mock updateStatus 函數(shù),該怎么做?下面是它的測(cè)試代碼:
@Test public void testUpdateStatus() throws InterruptedException { // 初始化被測(cè)對(duì)象 MyTask myTask = PowerMockito.spy(new MyTask()); myTask.setOperation(new MyTask.Operation()); // 使用 doAnswer 來 mock updateStatus 函數(shù)的行為 PowerMockito.doAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); MyTask.Operation operation = (MyTask.Operation)args[0]; operation.setStatus("success"); return null; } }).when(myTask).updateStatus(Mockito.any(MyTask.Operation.class)); Assert.assertEquals(true, myTask.run()); }
上面的代碼中,我們使用 doAnswer
來 mock updateStatus
的行為,相當(dāng)于使用 answer
函數(shù)來替換原來的 updateStatus
函數(shù),在這里,我們將 operation
的狀態(tài)設(shè)置為了 "success
",以期待 myTask.run()
函數(shù)返回 true
。于是,我們又學(xué)會(huì)了如何 mock 具有輸出參數(shù)的函數(shù)了。
看完上述內(nèi)容,是不是對(duì)Java中UnitTest和PowerMock的詳細(xì)解析有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
分享題目:Java中UnitTest和PowerMock的詳細(xì)解析
轉(zhuǎn)載來于:http://muchs.cn/article16/piddgg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、App設(shè)計(jì)、網(wǎng)站策劃、用戶體驗(yàn)、網(wǎng)站維護(hù)、全網(wǎng)營(yíng)銷推廣
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)