歐美企業做開發除了英語是必備技能之外,使用Mockito寫unit test(單位測試) 也是必備技能,畢業經歷的四家外企,美企,德企,德企,瑞士企業,沒有一家不寫UT的,中間在一個私企過渡了一下,呵呵,UT是什么?最近,小伙伴們在寫UT時遇到了各種問題,借此機會我對UT做一個總結,希望小伙伴們在寫UT時不要繞彎路。示例的源碼可以直接通過csdn下載也可以通過git導出:https://github.com/igdnss/mockito.git
注:這里主要是介紹Mockito的使用,所以需要有一定的junit test 的基礎,文中關于junit test 不懂的地方歡迎評論,私信,第一時間解答。
UnitTest 介紹 Mockito介紹 代碼介紹 mock對象 深度mock對象 mock方法調用 有返回值方法調用 拋出異常方法調用 無返回值方法調用 迭代方法調用 thenAnswer處理業務邏輯 mock對象調用真實方法 spy 部分mock mock參數匹配 hamcrest 的使用 結束語
UnitTest 介紹
在介紹Mockito之前,強調Unit Test的幾點內容。
支持自動化 (這里需要通過jenkins來支持) 。 單元測試之間獨立不依賴 。 單元測試獨立于數據庫,文件,或者其進程,一個好的單元測試完全獨立于外界資源。 時間空間透明性,即不論何時何地執行,結果都應該一樣。 單元測試需要具有一定的意義,不要存粹的去追求測試覆蓋率。 單元測試追求簡短,且重要性等同于源代碼。 其中第3天提到ut要獨立于外界資源,這看起來很奇怪,沒有外界資源ut怎么能跑起來,這就是本篇要要介紹的重點。沒有外界資源,我們需要偽造出資源,也就是業內所說mock資源。目前可以提供mock的框架有很多,例如easymock,powermock以及本文要介紹了mockito。
常用標簽
本篇主要介紹Mockito,順便將junit test中使用在方法上的標簽也整理出來,方便后面讀代碼。 @BeforeClass 在所有類執行之前執行,例如設置上下文環境,共享變量等。 @Before 在當前類執行之前執行,例如初始化對象,變量等。 @After 在當前類執行之后執行。 @AfterClass 在所有類執行之后執行 @Test 需要測試業務邏輯 @Test(timeout=xxx) 在timeout時間范圍內如果沒有執行完測試邏輯,則報錯 @Test(expected=Exception.class) 設置當前的測試邏輯拋出的異常,異常類型為Exception.class @Ignore 忽略當前的測試邏輯,不執行
Mockito介紹
官網: https://site.mockto.org 不太喜歡這個官網,東西有點亂,但畢竟是官方的權威性,再亂也得硬著頭皮去看。mockito是一個測試框架,通過mock(模擬)外界資源讓測試代碼無需依賴真實環境就能跑起來,可以理解為讓測試代碼在虛擬環境中跑起來。接下來,我會將工作中常用到一些知識點全部整理出來,希望能幫助大家。依賴于自己寫的一個簡單三層架構的示例進行介紹,代碼的邏輯和環境很簡單,沒有具體的數據庫,沒有具體的業務邏輯,主要是為了介紹Mockito是如何將各個部分Mock出來并完成UT的。主要測試Controller層與Service層中的方法,有個別Mock的知識點可能沒有涉及到Demo中的方法,旨在介紹知識點。其中UnitTest和Mockito的版本分別為:4.13.2,1.10.19 注意 :mockito mock的資源不能是局部的,例如局總變量,私有方法是無法mock的。
代碼介紹
Controller 層: 通過此層去調用業務層,實現根據id查找用戶的接口
public class FindingController { UserService userService
; public FindingController ( UserService userService
) { this . userService
= userService
; } public String
getUser ( int id
) { User user
= null
; try { user
= userService
. findById ( id
) ; } catch ( UnsupportedOperationException e
) { return "Error" ; } if ( user
== null
) { return "No one" ; } else { return "Hello" ; } } public void clear ( ) { userService
. clear ( ) ; } public List
< User> getGroup ( ) { return userService
. getGroup ( ) ; } public int getAge ( ) { return userService
. getAge ( ) ; } }
Mapper層:
public class UserMapper { public User
findById ( int userId
) { throw new UnsupportedOperationException ( ) ; }
}
domain層:
public class Base {
}
public class Client extends Base { }
public class User extends Base { String name
; public String
getName ( ) { return name
; } public void setName ( String name
) { this . name
= name
; } }
**service層: **
public class UserService { UserMapper userMapper
; public User
findById ( int userId
) { User user
= userMapper
. findById ( userId
) ; return user
; } public void clear ( ) { throw new RuntimeException ( ) ; } public List
< User> getGroup ( ) { throw new RuntimeException ( ) ; } public int getAge ( ) { return 18 ; } public int getAgeByIdAndName ( int id
, String name
) { return 18 ; } public String
getName ( Base base
) { throw new RuntimeException ( ) ; } }
以上代碼真的是一位含苞待放的姑娘,一切的一切都是那么的干凈清爽,在這些的代碼基礎之上我們對mockito進行抽絲剝繭。
mock對象
三種最常用的方法。
mock() 使用此方法必須要在@RunWith(MockitoJUnitRunner.class)中傳入MockitoJUnitRunner.class 。這種法一般用于測試方法中局部對象的mock 示例
@Before public void setUp ( ) { findingController
= mock ( FindingController
. class ) ; }
@Mock 可以使用@Mock標簽,一般mock全局對象。 示例
@Mock private UserMapper userMapper
;
注:mock的對象只是一個空對象,其內部的屬性并沒有mock出來,所以直接使用mock的對象調用方法是會報空指針異常,因此使用mock的對象調用方法時,還需要mock方法的調用。或者在@Before中加上MockitoAnnotaion.initMocks(this);
深度mock對象
FindingController getUser()方法中調用了UserService的getUser()返回結果為User,如果這需要測試user.getName(),就得再需要mock一個User 對象。可以使用深度mock來解決這個問題。
@Mock ( answer
= Answers
. RETURNS_DEEP_STUBS
) private UserService userService
;
mock方法調用
有返回值方法調用
語法一: when(T methodCall).thenReturn(T returnValue); methodCall:方法調用;returnValue:調用此方法mock出的返回值。注:如果對象是mock出來的,必須要mock它的方法調用,否則會報空指針異常,因為mock的時候只mock一個空對象,其內部的屬性并沒有mock出來,所以報空指針異常。 語法二: doReturn(Object toBeReturned).when(T mock).方法 。 示例:
成功獲取id為1的用戶。
@Test public void testGetUserSuccess ( ) { when ( findingController
. getUser ( 1 ) ) . thenReturn ( successResult
) ; String result
= findingController
. getUser ( 1 ) ; assertEquals ( successResult
, result
) ; }
使用doReturn成功獲取id為1的用戶
@Test public void testGetUserSuccessWith1DoReturn ( ) { doReturn ( successResult
) . when ( findingController
) . getUser ( 1 ) ; String result
= findingController
. getUser ( 1 ) ; assertEquals ( successResult
, result
) ; }
成功獲取任意id的用戶
@Test public void testGetUserSuccessWithAnyInt ( ) { when ( findingController
. getUser ( anyInt ( ) ) ) . thenReturn ( successResult
) ; String result
= findingController
. getUser ( 1 ) ; assertEquals ( successResult
, result
) ; }
無深度mock對象調用方法
@Test public void testGetUserSuccessWithoutDeep ( ) { when ( userService
. findById ( 1 ) ) . thenReturn ( user
) ; User user
= userService
. findById ( 1 ) ; when ( user
. getName ( ) ) . thenReturn ( successResult
) ; String result
= user
. getName ( ) ; assertEquals ( successResult
, result
) ; }
深度mock對象調用方法 一步到位,無需多次mock
@Test public void testGetUserSuccessWithDeep ( ) { when ( userService
. findById ( 1 ) . getName ( ) ) . thenReturn ( successResult
) ; String userName
= userService
. findById ( 1 ) . getName ( ) ; assertEquals ( successResult
, userName
) ; }
拋出異常方法調用
語法一: when(T methodCall).thenThrow(Class<? extends Throwable>… throwableClasses);這種方法已經被derecated了,基本不用。 語法二: doThrow(Class<? extends Throwable> toBeThrown).when(T mock).方法; 示例:
@SuppressWarnings ( "unchecked" ) @Test public void testGetUserException1WithAnyInt ( ) { when ( findingController
. getUser ( anyInt ( ) ) ) . thenThrow ( UnsupportedOperationException
. class ) ; assertThrows ( UnsupportedOperationException
. class , ( ) - > { findingController
. getUser ( 1 ) ; } ) ; } @Test public void testGetUserExceptionWithAnyInt ( ) { doThrow ( UnsupportedOperationException
. class ) . when ( findingController
) . getUser ( anyInt ( ) ) ; assertThrows ( UnsupportedOperationException
. class , ( ) - > { findingController
. getUser ( 1 ) ; } ) ; }
無返回值方法調用
語法: doNothing().when(T mock).方法; 調用完方法后需要驗證此方法是被調用的次數。verify(T mock, VerificationMode mode).方法。其中mode表示方法被調用的次數,可以不傳,默認為times(1)。 示例:
@Test
public void testClear ( ) { doNothing ( ) . when ( findingController
) . clear ( ) ; findingController
. clear ( ) ; verify ( findingController
, times ( 1 ) ) . clear ( ) ; }
迭代方法調用
語法一: when(T methodCall).thenReturn(Object value,Object … values); 語法二: when(T methodCall).thenReturn(Object value,Object … values); 針對同一個方法,希望每一次的調用結果不同,就應該用到此方法 示例:
@Test public void testGetGroup1 ( ) { when ( findingController
. getGroup ( ) ) . thenReturn ( list
) ; when ( list
. size ( ) ) . thenReturn ( 1 , 2 , 3 , 4 ) ; assertEquals ( 1 , list
. size ( ) ) ; assertEquals ( 2 , list
. size ( ) ) ; assertEquals ( 3 , list
. size ( ) ) ; assertEquals ( 4 , list
. size ( ) ) ; } @Test public void testGetGroup2 ( ) { when ( findingController
. getGroup ( ) ) . thenReturn ( list
) ; when ( list
. size ( ) ) . thenReturn ( 1 ) . thenReturn ( 2 ) . thenReturn ( 3 ) . thenReturn ( 4 ) ; assertEquals ( 1 , list
. size ( ) ) ; assertEquals ( 2 , list
. size ( ) ) ; assertEquals ( 3 , list
. size ( ) ) ; assertEquals ( 4 , list
. size ( ) ) ; }
thenAnswer處理業務邏輯
語法: when(T methodCall).thenAnswer(Answer<?> answer) 當有一些結果為動態結果時,就要用這種方式了。 示例:
@Test public void testDoAnswer ( ) { when ( findingController
. getGroup ( ) ) . thenReturn ( list
) ; when ( list
. get ( anyInt ( ) ) ) . thenAnswer ( answer
- > { Integer index
= answer
. getArgumentAt ( 0 , Integer
. class ) ; return String
. valueOf ( index
* 5 ) ; } ) ; assertEquals ( "0" , list
. get ( 0 ) ) ; assertEquals ( "50" , list
. get ( 10 ) ) ; }
mock對象調用真實方法
語法: when(T methodCall).thenCallRealMethod(); 使用mock對象調用真實的方法就需要使用到此種方式。注意:這里調用的方法不能是抽象方法 示例:
@Test public void testGetAge ( ) { when ( userService
. getAge ( ) ) . thenCallRealMethod ( ) ; assertEquals ( 18 , userService
. getAge ( ) ) ; }
spy 部分mock
語法: Object o = spy(真實對象); 在有些測試場景下,需要測試真實對象的值,對部分只能使用mock來處理,可以借助spy來完成。 示例:
public void testSpy ( ) { List
< String> userNameList
= new ArrayList < String> ( ) ; List
< String> spyList
= spy ( userNameList
) ; spyList
. add ( "A" ) ; spyList
. add ( "B" ) ; spyList
. add ( "C" ) ; assertEquals ( "A" , spyList
. get ( 0 ) ) ; assertEquals ( "B" , spyList
. get ( 1 ) ) ; assertEquals ( "C" , spyList
. get ( 2 ) ) ; assertEquals ( false , spyList
. isEmpty ( ) ) ; when ( spyList
. isEmpty ( ) ) . thenReturn ( true ) ; when ( spyList
. size ( ) ) . thenReturn ( 100 ) ; assertEquals ( true , spyList
. isEmpty ( ) ) ; assertEquals ( 100 , spyList
. size ( ) ) ; }
mock參數匹配
在前面的示例中提到參數匹配,就是當調用一個方法時,應該返回什么樣的值。這里主要介紹一下以下幾種方式。
isA()類型判斷
示例: 測試getName()中傳入的是否為Base類型的對象
@Test public void testGetNameWithIsA ( ) { when ( userService
. getName ( isA ( Base
. class ) ) ) . thenReturn ( successResult
) ; String name
= userService
. getName ( new User ( ) ) ; assertEquals ( successResult
, name
) ; }
any類型判斷
這個方法我基本沒有用過,感覺意義不大 示例:
@Test public void testGetNameWithAny ( ) { when ( userService
. getName ( any ( User
. class ) ) ) . thenReturn ( successResult
) ; String name
= userService
. getName ( new Client ( ) ) ; assertEquals ( successResult
, name
) ; }
通配符的使用
前文中使用過通配符anyInt(), Mockito中提供了好幾個通配符,這里舉兩個例子。使用通配符的時候一定要注意,參數列表中一處使用通配符,就得處處使用通配符,如果想傳入特定的值一定要使用eq()方法。通配符的優先級大于eq(),如果同樣的方法多個mock,通配符寫在最后一句的話會覆蓋前面mock的結果。 示例:
@Test public void testGetNameByIdAndName ( ) { when ( userService
. getAgeByIdAndName ( anyInt ( ) , anyString ( ) ) ) . thenReturn ( 100 ) ; int result1
= userService
. getAgeByIdAndName ( 1 , "A" ) ; assertEquals ( 100 , result1
) ; when ( userService
. getAgeByIdAndName ( anyInt ( ) , eq ( "B" ) ) ) . thenReturn ( 200 ) ; int result2
= userService
. getAgeByIdAndName ( 1 , "B" ) ; assertEquals ( 200 , result2
) ; }
hamcrest 的使用
hamcrest中提供了很多強大的功能,配合assertThat()使用會增加代碼的可讀性,代碼編寫風格符合陳述式風格。另外,可以不用使用assertTure(), assertEquals()等斷言方法,使用一個assertThat()就可以了。以下示例獨立于文章開頭的業務代碼。但assertThat已經deprecated了,不知道為什么。 示例:
@Test public void testHamcrest ( ) { int i
= 20 ; double price
= 13.2 ; assertThat ( i
, equalTo ( 20 ) ) ; assertThat ( i
, not ( equalTo ( 210 ) ) ) ; assertThat ( i
, is ( 20 ) ) ; assertThat ( i
, is ( not ( 100 ) ) ) ; assertThat ( price
, either ( equalTo ( 13.2 ) ) . or ( equalTo ( 10 ) ) ) ; assertThat ( price
, anyOf ( is ( 5 ) , is ( 13.2 ) , not ( 18 ) ) ) ; }
結束語
終于寫完了,真心不容易,不過挺開心。Mockito中常用點都在這里了,當然Mockito中還有很多知識點,大家可以參考官網。希望本文能幫助大家,祝大家在IT之路上少走彎路,一路綠燈不堵車,測試一性通過,bug秒解!
總結
以上是生活随笔 為你收集整理的欧美企业必备技能-Mockito 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。