1. 程式人生 > >【轉】關於java 單元測試Junit4和Mock的一些總結

【轉】關於java 單元測試Junit4和Mock的一些總結

原文出處請點選這裡

1. 單元測試的必要性

最近專案有在寫java程式碼的單元測試,然後在思考一個問題,為什麼要寫單元測試??單元測試寫了有什麼用??百度了一圈,如下:

  • 軟體質量最簡單、最有效的保證;
  • 是目的碼最清晰、最有效的文件;
  • 可以優化目的碼的設計;
  • 是程式碼重構的保障;
  • 是迴歸測試和持續整合的基石。

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

2. Junit4 與 Mock 的介紹

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

問題一:為什麼需要mock或stub?它與junit什麼關係?

在做單元測試的時候,我們會發現我們要測試的方法會引用很多外部依賴的物件,比如:(傳送郵件,網路通訊,記錄Log, 檔案系統 之類的)。 而我們沒法控制這些外部依賴的物件。 為了解決這個問題,我們需要用到Stub和Mock來模擬這些外部依賴的物件,從而控制它們。

JUnit是單元測試框架,可以輕鬆的完成關聯依賴關係少或者比較簡單的類的單元測試,但是對於關聯到其它比較複雜的類或對執行環境有要求的類的單元測試,模擬環境或者配置環境會非常耗時,實施單元測試比較困難。而這些“mock框架”(Mockito 、jmock 、 powermock、EasyMock),可以通過mock框架模擬一個物件的行為,從而隔離開我們不關心的其他物件,使得測試變得簡單。(例如service呼叫dao,即service依賴dao,我們可以通過mock dao來模擬真實的dao呼叫,從而能達到測試service的目的。)

模擬物件(Mock Object)可以取代真實物件的位置,用於測試一些與真實物件進行互動或依賴於真實物件的功能,模擬物件的背後目的就是建立一個輕量級的、可控制的物件來代替測試中需要的真實物件,模擬真實物件的行為和功能。

問題二:mock與stub什麼區別?

Mock和Stub是兩種測試程式碼功能的方法。Mock測重於對功能的模擬,Stub測重於對功能的測試重現。比如對於List介面,Mock會直接對List進行模擬,而Stub會新建一個實現了List的TestList,在其中編寫測試的程式碼。
強烈建議優先選擇Mock方式,因為Mock方式下,模擬程式碼與測試程式碼放在一起,易讀性好,而且擴充套件性、靈活性都比Stub好。

其中EasyMock和Mockito對於Java介面使用介面代理的方式來模擬,對於Java類使用繼承的方式來模擬(也即會建立一個新的Class類)。Mockito支援spy方式,可以對例項進行模擬。但它們都不能對靜態方法和final類進行模擬,powermock通過修改位元組碼來支援了此功能。

有篇文章介紹:http://blog.csdn.net/devhubs/article/details/8018084

二、junit4相關介紹

這裡有篇文章介紹了junit4的一些,包括怎麼引入,使用,蠻詳細。—》 http://blog.csdn.net/happylee6688/article/details/38069761

這邊就記錄一些常用註解,當做學習方便。

常用註解
@Before:初始化方法,在任何一個測試方法執行之前,必須執行的程式碼。對比 JUnit 3 ,和 setUp()方法具有相同的功能。在該註解的方法中,可以進行一些準備工作,比如初始化物件,開啟網路連線等。

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

@Test:測試方法,表明這是一個測試方法。在 JUnit 中將會自動被執行。對與方法的宣告也有如下要求:名字可以隨便取,沒有任何限制,但是返回值必須為 void ,而且不能有任何引數。如果違反這些規定,會在執行時丟擲一個異常。不過,為了培養一個好的程式設計習慣,我們一般在測試的方法名上加 test ,比如:testAdd()。
同時,該 Annotation(@Test) 還可以測試期望異常和超時時間,如 @Test(timeout=100),我們給測試函式設定一個執行時間,超過這個時間(100毫秒),他們就會被系統強行終止,並且系統還會向你彙報該函式結束的原因是因為超時,這樣你就可以發現這些 bug 了。而且,它還可以測試期望的異常,例如,我們剛剛的那個空指標異常就可以這樣:@Test(expected=NullPointerException.class)。

@Ignore:忽略的測試方法,標註的含義就是“某些方法尚未完成,咱不參與此次測試”;這樣的話測試結果就會提示你有幾個測試被忽略,而不是失敗。一旦你完成了相應的函式,只需要把 @Ignore 註解刪除即可,就可以進行正常測試了。當然,這個 @Ignore 註解對於像我這樣有“強迫症”的人還是大有意義的。每當看到紅色條(測試失敗)的時候就會全身不舒服,感覺無法忍受(除非要測試的目的就是讓它失敗)。當然,對程式碼也是一樣,無法忍受那些雜亂不堪的程式碼。

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

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

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

三、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容器環境下執行

@ContextConfiguration(locations = {“classpath:applicationContext.xml”} —>用來指明Spring的配置檔案位置

Mockito簡單運用說明

① when(mock.someMethod()).thenReturn(value):設定mock物件某個方法呼叫時的返回值。可以連續設定返回值,即when(mock.someMethod()).thenReturn(value1).then
Return(value2),第一次呼叫時返回value1,第二次返回value2。也可以表示為如下:
when(mock.someMethod()).thenReturn(value1,value2)。
② 呼叫以上方法時丟擲異常: when(mock.someMethod()).thenThrow(new Runtime
Exception());
③ 另一種stubbing語法:
doReturn(value).when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
④ 對void方法進行方法預期設定只能用如下語法:
doNothing().when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
doNothing().doThrow(new RuntimeException()).when(mock.someMethod())
⑤ 方法的引數可以使用引數模擬器,可以將anyInt()傳入任何引數為int的方法,即anyInt匹配任何int型別的引數,anyString()匹配任何字串,anySet()匹配任何Set。
⑥ Mock物件只能呼叫stubbed方法,調用不了它真實的方法,但是Mockito可以用spy來監控一個真實物件,這樣既可以stubbing這個物件的方法讓它返回我們的期望值,又可以使得對其他方法呼叫時將會呼叫它的真實方法。
⑦ Mockito會自動記錄自己的互動行為,可以用verify(…).methodXxx(…)語法來驗證方法Xxx是否按照預期進行了呼叫。
(1) 驗證呼叫次數:verify(mock,times(n)).someMethod(argument),n為被呼叫的次數,如果超過或少於n都算失敗。除了times(n),還有never(),atLease(n),atMost(n)。
(2) 驗證超時:verify(mock, timeout(100)).someMethod();
(3) 同時驗證:verify(mock, timeout(100).times(1)).someMethod();

相關注解:

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類似的註解方式,對私有的成員進行賦值,此時無法直接對私有的依賴設定mock物件。可以通過引入ReflectionTestUtils,解決依賴注入的問題。

(不是很理解。。。。,因為我對某個service的private dao,直接mock,並沒有設定ReflectionTestUtils.setField(),照樣可以執行ok,那麼這個什麼時候用到?。)

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

這個註解不會把一個類變成mock或是spy,但是會把當前物件下面的Mock/Spy類注入進去,按型別注入。

@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可以完成對一般物件方法的模擬,但是對於靜態函式、建構函式、私有函式等還是無能為力.