日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

单元测试之带你搞懂Mockito使用

發(fā)布時(shí)間:2024/4/11 编程问答 64 豆豆
生活随笔 收集整理的這篇文章主要介紹了 单元测试之带你搞懂Mockito使用 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Mock介紹

在平時(shí)開發(fā)過程中,我們往往會(huì)遇到以下問題
1.由于依賴調(diào)用的接口沒有開發(fā)完成,需要等待(客戶端和服務(wù)端,服務(wù)端和其他服務(wù)之間)
2.自測時(shí)由于服務(wù)器故障等無法正常調(diào)用接口,或者一些邊界條件無法在測試環(huán)境模擬數(shù)據(jù)
3.同樣的單元測試,當(dāng)依賴的數(shù)據(jù)發(fā)生變化時(shí),無法反復(fù)執(zhí)行,不能在上線前對之前的功能進(jìn)行自動(dòng)回歸

mock就幫我們解決了以上問題

mock的定義(what)
mock是在測試過程中,對于一些不容易構(gòu)造/獲取的對象,創(chuàng)建一個(gè)mock對象來模擬對象的行為

哪些時(shí)機(jī)和場合需要使用mock(when&where):
1.單元測試/接口測試中測試對象依賴其他對象,這些對象的構(gòu)造復(fù)雜、耗時(shí)或者根本無法構(gòu)造(未交付)
2.我們只測試對象內(nèi)部邏輯的質(zhì)量,不關(guān)心依賴對象的邏輯正確性和穩(wěn)定性
3.一些邊界條件無法在正常情形下模擬 比方說接口異常返回

使用mock的時(shí)候并不需要對所有的依賴服務(wù)(對象)都進(jìn)行Mock,只需要對不容易構(gòu)造的對象或者不穩(wěn)定的對象進(jìn)行mock可以了。當(dāng)然,mock的前提是底層服務(wù)提供的數(shù)據(jù)是值得信任的,實(shí)際開發(fā)過程還是需要進(jìn)行聯(lián)調(diào)測試的。

像EasyMock , Mockito , PowerMock都是常用的mock框架

powerMock是基于easyMock或Mockito擴(kuò)展出來的增強(qiáng)版本,能夠mock靜態(tài)、final、私有方法等,這些都是EasyMock和Mockito不能做到的。

Mockito和EasyMock的功能差不多,但是Mockito不需要錄制、播放這些動(dòng)作,語法上比EasyMock更靈活,可讀性更好
個(gè)人比較喜歡Mockito和PowerMock

今天就先來簡單講講關(guān)于Mockito的使用,之后會(huì)單獨(dú)再講下PowerMock的增強(qiáng)點(diǎn),就是對于靜態(tài)、final、私有方法的mock

Mockito使用

1.Maven項(xiàng)目,需要先引入如下pom
<dependency><groupId>org.mockito</groupId><artifactId>mockito-all</artifactId><version>1.9.5</version><scope>test</scope> </dependency>

你也可以使用mockito-core的pom,不過mockito-core里只包含mockito類,而mockito-all會(huì)包含mockito類以及一些依賴項(xiàng),比方說hamcrest。
實(shí)際上mockito-all已經(jīng)停止更新,Mockito 2中已停止使用“mockito-all”發(fā)行版。*。

這里我使用的是mockito-all,兩個(gè)pom創(chuàng)建代理對象的方式不一樣,Mockito1是通過CGlib,而Mockito2是使用的ByteBuddy,常用的api都沒有區(qū)別,我后面提到的用法兩個(gè)pom都支持

2.@Mock和@InjectMocks注解

@Mock : 為某個(gè)類創(chuàng)建Mock對象,使用方式如下(通常直接加在屬性字段上):

@Mockprivate GetAirportTransferOrderHandler getAirportTransferOrderHandler;

等價(jià)于

GetAirportTransferOrderHandler singleMock = Mockito.mock(GetAirportTransferOrderHandler.class);

@InjectMocks

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

也就是說為被測試對象自動(dòng)注入mock或者spy的字段 (有點(diǎn)類似于spring的依賴注入的感覺),注入的方式有三種:構(gòu)造方法注入,setter注入,屬性注入

3.Mockito初始化的方式


官方文檔上的意思就是如果使用了@Mock和@InjectMocks注解,那么我們就需要調(diào)用MockitoAnnotations.initMocks(testClass)來幫助我們完成mock對象的創(chuàng)建和自動(dòng)注入

Mockito初始化的方式有三種:

//方式一 @Before public void init() {MockitoAnnotations.initMocks(this); } //方式二 @RunWith(MockitoJUnitRunner.class) //方式三 @Rule public MockitoRule mockito = MockitoJUnit.rule()

三者的使用效果都是一樣的,主要是為了
1.提供mock初始化工作
2.為unit test提供框架使用的自動(dòng)驗(yàn)證

使用方式一或者方式三的好處在于不需要使用Mockito的Runner,就可以使用其他的Runner了,比方說SpringJUnit4ClassRunner

4.Stubbing 插樁

設(shè)置mock對象的某個(gè)方法返回期望值
when(mockedObject.method() ).thenReturn( expectValue);

對于沒有stub過的有返回值的方法,會(huì)返回默認(rèn)值(0,false,null等)

@RunWith(MockitoJUnitRunner.class) public class GetAirportTransferOrderListCiTest {@InjectMocksprivate GetAirportTransferOrderListProcessor getAirportTransferOrderListProcessor;@Mockprivate GetAirportTransferOrderHandler getAirportTransferOrderHandler;@Testpublic void testGetAirportTransferOrderList() throws Throwable {GetAirportTransferOrderListRequestType getAirportTransferOrderListRequestType = new GetAirportTransferOrderListRequestType();getAirportTransferOrderListRequestType.setUid("test");getAirportTransferOrderListRequestType.setLocale("zh-HK");getAirportTransferOrderListRequestType.setOrderIds(Lists.newArrayList(1212));when(getAirportTransferOrderHandler.handle(anyObject())).thenReturn(getAirportTransferOrderList());GetAirportTransferOrderListResponseType responseType = getAirportTransferOrderListProcessor.execute(getAirportTransferOrderListRequestType);Assert.assertTrue(responseType.getOrderInfos().size() == 1);}

可以調(diào)用方法時(shí)模擬拋出異常,適合于模擬第三方接口或者服務(wù)故障情形
when(RedisManager.getInstance()).thenThrow(new Exception(“error get redis connection”));

對于沒有返回值的方法,可以通過如下方式來進(jìn)行插樁
Mockito.doNothing().when(mockObject).voidMethod(param);
也可以使用doAnswer來插樁

doAnswer(new Answer() {public Object answer(InvocationOnMock invocation) {String username = (String) invocation.getArguments()[0];System.out.println(username);return null;}}).when(userService).delete("a","a");//這里的delete是一個(gè)沒有返回值的方法userService.delete("a","a");

Answer由于可以獲取到方法調(diào)用的參數(shù)信息,使用doAnswer我們還可以根據(jù)方法的調(diào)用參數(shù)返回不同的結(jié)果

doAnswer(new Answer() {public Object answer(InvocationOnMock invocation) {String username = (String) invocation.getArguments()[0];if(!StringUtils.isEmpty(username)){return "有姓名";}return "沒有姓名";}}).when(userService).show(anyString(),anyString());
5.Argument Matchers 參數(shù)匹配

Mockito的參數(shù)匹配有兩種方式:
1.傳入實(shí)際的參數(shù)
判斷是否匹配的時(shí)候直接通過equals方法進(jìn)行比較

when(commonService.getCityName(2,“en-US”)).thenReturn(“shanghai”);

2.使用arguments matchers對象

eg.anyInt(),anyString(),anyObject(),anySet(),eq()等

下面的參數(shù)匹配方式就是說明,對于調(diào)用getAirportTransferOrderHandler的handle方法,無論是什么參數(shù)都返回getAirportTransferOrderList()的數(shù)據(jù)結(jié)果

when(getAirportTransferOrderHandler.handle(anyObject())).thenReturn(getAirportTransferOrderList());

注意上述兩種參數(shù)匹配方式是不能混用的
when(commonService.getCityName(eq(2),“en-US”)).thenReturn(“shanghai”); 錯(cuò)誤
when(commonService.getCityName(eq(2),eq(“en-US”)).thenReturn(“shanghai”); 正確
when(commonService.getCityName(eq(2),anyString()).thenReturn(“shanghai”); 正確

如果我們需要自己定義參數(shù)的驗(yàn)證規(guī)則,Mockito還提供了custom argument matchers – argThat
使用如下:

when(updateDataHandler.handle(argThat(new ArgumentMatcher<UpdateDataRequestType>() {@Overridepublic boolean matches(Object o) {//可以定義自己想要的任何匹配邏輯UpdateDataRequestType request = (UpdateDataRequestType) o;if (!request.getType().equals("CP")){return false ;}return true;}}))).thenReturn(response);
6.verify 驗(yàn)證mock方法被調(diào)用了特定次數(shù)/至少x次/最多x次/從未被調(diào)用
//是否add("twice")被調(diào)用了兩次。 verify(mockedList, times(2)).add("twice"); //驗(yàn)證add("twice")被調(diào)用了至少一次 等價(jià)于verify(mockedList).add("twice"); verify(mockedList, atLeastOnce()).add("twice"); verify(mockedList, atLeast(2)).add("twice"); verify(mockedList, atMost(5)).add("twice"); verify(mockedList, never()).add("twice");

我在開發(fā)過程中有時(shí)會(huì)使用verify來驗(yàn)證緩存是否生效,調(diào)用兩次代碼,如果mock只被執(zhí)行了一次,說明第二次緩存命中

7.Spy

You can create spies of real objects. When you use the spy then the real methods are called (unless a method was stubbed).

關(guān)于spy官方的定義寫的很清楚,spy是用于創(chuàng)建對應(yīng)的真實(shí)對象,如果對應(yīng)的方法我們自己不主動(dòng)進(jìn)行插樁的話就會(huì)執(zhí)行對應(yīng)的真實(shí)業(yè)務(wù)代碼

對于Spy的對象,他調(diào)用的都是真實(shí)方法,如果你想讓他返回的值按照自己設(shè)定的來,就需要自己去mock
適合于對象內(nèi)有大量的調(diào)用了大量的方法,但實(shí)際只需要mock關(guān)注的少量方法即可

看下我實(shí)際使用的一個(gè)例子:

使用@Mock生成的類,默認(rèn)所有方法都不是真實(shí)的方法,而且返回值都是NULL。
使用@Spy生成的類,默認(rèn)所有方法都是真實(shí)方法,返回值都是和真實(shí)方法一樣的。

當(dāng)用when去設(shè)置mock返回值時(shí),它里面的方法(cache.get(XX))會(huì)先執(zhí)行一次。使用doReturn去設(shè)置的話,就不會(huì)產(chǎn)生上面的問題

所以如果需要對Spy的對象進(jìn)行mock的時(shí)候,推薦都直接使用doReturn或者doThrow的句式,否則就會(huì)先調(diào)用一次真實(shí)的業(yè)務(wù)邏輯。如果你的數(shù)據(jù)有些初始化操作沒有執(zhí)行,那么就可能出現(xiàn)異常

像下面這種情形,由于spy的List并沒有添加任何元素,直接執(zhí)行spy.get(0)就會(huì)發(fā)生數(shù)組越界問題

List list = new LinkedList();List spy = spy(list);//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)when(spy.get(0)).thenReturn("foo");//You have to use doReturn() for stubbingdoReturn("foo").when(spy).get(0);

注意:由于@Spy是監(jiān)控的真實(shí)對象,我們需要先得到一個(gè)真實(shí)的對象,才可以對它使用spy的功能。一般可以通過spring容器幫我們生成bean或者自己手動(dòng)new創(chuàng)建對象

8.同一個(gè)mock多次調(diào)用相同方法返回不同的結(jié)果
Mockito.when(methodCall).thenReturn(result1).thenReturn(result2).thenReturn(resultx) //簡化寫法 Mockito.when(methodCall).thenReturn(result1,result2,resultx)

這個(gè)適合于需要mock迭代器的場景,或者同樣的方法在執(zhí)行過程中需要多次調(diào)用,當(dāng)然對于這種情形是可以通過不同的參數(shù)匹配和doAnswer來解決的

9.鏈?zhǔn)秸{(diào)用的mock

一般情形下我們都是創(chuàng)建一個(gè)mock對象,然后對其進(jìn)行插樁,但是有的的情形下我們可能需要mock一個(gè)鏈?zhǔn)秸{(diào)用的對象,比方說建造者模式下的Builder
我們可以這樣寫

public class User {private String username;private String password;public User(String username, String password) {this.username = username;this.password = password;}public String show() {return "username:"+ username+",password:"+password;} } public class UserService {public String show(String name ,String a) {return "1";}public void delete(String username, String password) {System.out.println("刪除成功....");}public User getUser(){return new User("aa","bb");} } UserService userService = Mockito.mock(UserService.class);User user = Mockito.mock(User.class);Mockito.when(userService.getUser()).thenReturn(user);Mockito.when(user.show()).thenReturn("mock show");Assert.assertEquals("mock show",userService.getUser().show());

對于一兩次的鏈?zhǔn)秸{(diào)用還好,如果次數(shù)多了,mock起來就會(huì)比較麻煩,Mockito提供了Deep Stub的功能
上面的寫法等價(jià)于下面的

UserService userService= Mockito.mock(UserService.class,RETURNS_DEEP_STUBS);Mockito.when(userService.getUser().show()).thenReturn("mock test");Assert.assertEquals("mock test",userService.getUser().show());

參考文檔:
https://javadoc.io/doc/org.mockito/mockito-core/2.26.0/org/mockito/Mockito.html
翻譯版:
Mockito 中文文檔 ( 2.0.26 beta )

總結(jié)

以上是生活随笔為你收集整理的单元测试之带你搞懂Mockito使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。