一、背景
接著上一篇文章:?jiǎn)卧獪y(cè)試入門篇,本篇文章作為單元測(cè)試的進(jìn)階篇,主要介紹如何對(duì)Springboot Service層代碼做單元測(cè)試,以及單元測(cè)試中涉及外調(diào)服務(wù)時(shí),如何通過Mock完成測(cè)試。
二、Springboot Service層代碼單元測(cè)試
現(xiàn)在項(xiàng)目都流行前后端代碼分離,后端使用springboot框架,在service層編寫接口代碼實(shí)現(xiàn)邏輯。假設(shè)現(xiàn)在前端不是你寫的,你要對(duì)你自己寫的后端springboot service層提供的接口方法做單元測(cè)試,以確保你寫的代碼是能正常工作的。
Service層代碼單元測(cè)試:一個(gè)簡(jiǎn)單的service調(diào)mapper查詢數(shù)據(jù)庫replay_bug表數(shù)據(jù)量的接口功能
ReplayBugServiceImpl類代碼:
@Service
public class ReplayBugServiceImpl implements ReplayBugService {
@Autowired
ReplayBugMapper replayBugMapper;
@Override
public int queryBugTotalCount() {
return replayBugMapper.queryBugTotalCount();
}
}
replayBugMapper.queryBugTotalCount代碼:
@Select("select count(1) from replay_bug")
int queryBugTotalCount();
單元測(cè)試ReplayBugServiceImplTest類代碼:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReplayBugServiceImplTest{
@Autowired
ReplayBugServiceImpl replayBugService;
@Test
public void queryBugTotalCount() {
int bugCount=replayBugService.queryBugTotalCount();
System.out.println("結(jié)果是:"+bugCount);
}
}
代碼很簡(jiǎn)單,調(diào)用這個(gè)接口服務(wù),打印輸出,測(cè)試是否能正確查出數(shù)據(jù)。其中關(guān)鍵的兩個(gè)注解解釋:
@RunWith(SpringRunner.class)注解:是一個(gè)測(cè)試啟動(dòng)器,可以加載SpringBoot測(cè)試注解。
讓測(cè)試在Spring容器環(huán)境下執(zhí)行。如測(cè)試類中無此注解,將導(dǎo)致service、dao等自動(dòng)注入失敗。
@SpringBootTest注解:目的是加載ApplicationContext,啟動(dòng)spring容器。
測(cè)試結(jié)果如下:
更進(jìn)一步,測(cè)試帶入?yún)⒌膕ervice接口:新增接口單元測(cè)試
import com.test.service.BestTest;
import com.test.domain.UrlWhiteListVO;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReplayUrlWhiteListServiceImplTest{
@Autowired
ReplayUrlWhiteListServiceImpl replayUrlWhiteListService;
private UrlWhiteListVO urlWhiteListVO;
@Before
public void setup(){
urlWhiteListVO=new UrlWhiteListVO();
urlWhiteListVO.setAppId(78);
urlWhiteListVO.setAppName("testAPP");
urlWhiteListVO.setUrlWhite("http://www.baidu.com");
urlWhiteListVO.setRemarks("測(cè)試一下");
}
@Test
public void save() {
System.out.println("測(cè)試結(jié)果:"+replayUrlWhiteListService.save(urlWhiteListVO));
}
}
比之前多了一個(gè)@Before注解,下面自行設(shè)置不同的參數(shù)值,測(cè)試是否在各種入?yún)⑶闆r下接口代碼都沒有問題。
單元測(cè)試結(jié)果:
數(shù)據(jù)庫檢查數(shù)據(jù)插入成功:
三、單元測(cè)試使用Mockito完成Mock測(cè)試
實(shí)際業(yè)務(wù)代碼中可能會(huì)調(diào)到其他第三方接口、會(huì)和數(shù)據(jù)庫有交互,如果要測(cè)試跑通一個(gè)場(chǎng)景,準(zhǔn)備數(shù)據(jù)會(huì)非常麻煩。而單元測(cè)試很多時(shí)候,我們只關(guān)心自己的代碼邏輯是否有漏洞,這個(gè)使用就需要用到Mock, 不真實(shí)調(diào)用,而是將外調(diào)的接口、數(shù)據(jù)庫層面都Mock返回自己想要的各類假數(shù)據(jù)。
因此再進(jìn)一步,單元測(cè)試使用Mockito完成Mock測(cè)試:
import com.test.dao.ReplayBugMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReplayBugServiceImplMockTest {
/**
* 使用@Autowired是讓實(shí)例對(duì)象正常注入
* 使用@InjectMocks是為了向里面添加@Mock注入的對(duì)象
* */
@Autowired
@InjectMocks
ReplayBugServiceImpl replayBugService;
@Mock
ReplayBugMapper replayBugMapper;
@Before
public void setup() {
//添加Mock注解初始化
MockitoAnnotations.initMocks(this);
}
@Test
public void queryBugTotalCount() {
int count=1;
Mockito.when(replayBugMapper.queryBugTotalCount()).thenReturn(count);
int bugCount=replayBugService.queryBugTotalCount();
System.out.println("Mock單元測(cè)試返回的結(jié)果是:"+bugCount);
}
}
同樣的接口,之前真實(shí)調(diào)用數(shù)據(jù)庫的時(shí)候,我們看到返回的結(jié)果是3。本次Mock測(cè)試代碼中我們定義了count為1,使用Mockito讓數(shù)據(jù)庫調(diào)用直接Mock返回我們定義的1,不再真實(shí)調(diào)用數(shù)據(jù)庫。
測(cè)試結(jié)果:
Mockito介紹:Mockito是一款用于java開發(fā)的mock測(cè)試框架,用于快速創(chuàng)建和配置mock對(duì)象。通過創(chuàng)建外部依賴的 Mock 對(duì)象, 然后將此 Mock 對(duì)象注入到測(cè)試類中,簡(jiǎn)化有外部依賴的類的測(cè)試。
Mockito使用:在項(xiàng)目pom.xml中引入依賴spring-boot-starter-test,內(nèi)部就依賴了Mockito
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
測(cè)試代碼中用到Mockito的注解作用解釋:
@InjectMocks:讓@Mock(或@Spy)注解創(chuàng)建的mock將被注入到用該實(shí)例中。
@Mock:對(duì)函數(shù)的調(diào)用均執(zhí)行mock,不執(zhí)行真實(shí)調(diào)用。
如果只想對(duì)某一些外調(diào)做mock,其他的外調(diào)都走真實(shí)調(diào)用:
比如Service ReplayServiceImpl中方法如下
public int addBug(ReplayVO replayVO) {
if(replayManageMapper.addBug(replayVO.getId())==1){
//判斷如果replay_bug表中已經(jīng)有這條數(shù)據(jù),不再重復(fù)添加。應(yīng)對(duì)場(chǎng)景是用戶多次點(diǎn)擊標(biāo)記記錄為待解決bug。
if(replayBugService.existBugRecords(replayVO)>=1){
log.info("replay_bug表中數(shù)據(jù)已存在,不再重復(fù)插入數(shù)據(jù)");
return 1;
}else{
log.info("向replay_bug表中插入數(shù)據(jù)");
return replayManageMapper.saveToReplayBug(replayVO.getAppId(),replayVO.getRequestId(),replayVO.getId(),replayVO.getAppName(),replayVO.getSysDomain(),replayVO.getSysUrl(),replayVO.getUserAccount(),replayVO.getParameters(),replayVO.getResponse(),replayVO.getReplayStatus(),CommonUtils.convertDateToTime(replayVO.getReplayTime()));
}
}else{
return 0;
}
}
第一步先調(diào)用replayManageMapper.addBug對(duì)replay表中的這條數(shù)據(jù)更新狀態(tài),更新成功后返回1。
第二步再調(diào)用replayBugService.existBugRecords判斷replay_bug表中是否存在該條記錄,如果存在就不再重復(fù)插入。
第三步如果不存在,就再調(diào)用replayManageMapper.saveToReplayBug,向replay_bug表中插入該條記錄。
現(xiàn)在的需求是單元測(cè)試時(shí),對(duì)第二步外調(diào)的其他接口服務(wù)replayBugService做Mock處理,對(duì)數(shù)據(jù)庫相關(guān)的操作不做Mock,真實(shí)調(diào)用。
單元測(cè)試代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReplayServiceImplTest {
private ReplayVO replayVO;
@Autowired
@InjectMocks
ReplayServiceImpl replayService;
@Mock
ReplayBugService replayBugService;
@Before
public void setUp() {
//添加Mock注解初始化
MockitoAnnotations.initMocks(this);
replayVO=new ReplayVO();
replayVO.setAppId(1);
replayVO.setRequestId(2);
replayVO.setId(111);
replayVO.setAppName("testApp");
replayVO.setSysDomain("www.test.com");
replayVO.setSysUrl("http://www.test.com/queryList");
replayVO.setUserAccount("測(cè)試人員");
replayVO.setParameters("{\"userID\":\"123\"}");
replayVO.setResponse("{\"result\":\"成功\"}");
replayVO.setReplayStatus(5);
Date date =new Date();
replayVO.setReplayTime(date);
}
@Test
public void addBug() {
Mockito.when(replayBugService.existBugRecords(replayVO)).thenReturn(5);
System.out.println("返回值:"+replayService.addBug(replayVO));
}
}
代碼解釋:
ReplayBugService做Mock處理,所以加了注解@Mock;
ReplayServiceImpl中,由于需要部分外調(diào)服務(wù)Mock,部分外調(diào)服務(wù)不Mock,所以需要加上注解@Autowired和@InjectMocks:
使用@Autowired是讓實(shí)例對(duì)象正常注入;
使用@InjectMocks是為了向里面添加@Mock注入的對(duì)象;
當(dāng)replayBugService.existBugRecords(replayVO), Mock返回5,測(cè)試結(jié)果為:
當(dāng)replayBugService.existBugRecords(replayVO), Mock返回0,測(cè)試結(jié)果為:
數(shù)據(jù)庫查看,數(shù)據(jù)成功插入:
順帶說一下Mockito的@Spy與@Mock區(qū)別:
@Spy修飾的外部類,必須是真實(shí)存在的,如果沒有我們要自己生成創(chuàng)建
Mockito.doReturn(response).when(testService).save(Mockito.any());
@Mock修飾的外部類,是完全模擬出來的,就算項(xiàng)目中沒有這個(gè)類的實(shí)例,也能自己mock出來一個(gè)。
比如Spring項(xiàng)目中如果你引入了一個(gè)外部的Service:
- 如果在寫單元測(cè)試時(shí)候,外部的Service能加載到的話就可以使用@Spy注解,因?yàn)镾pring能為你從外部服務(wù)找到這個(gè)Service并生成實(shí)例注入。
- 但是如果外部的服務(wù)沒有部署,那么Spring就不能為你創(chuàng)建實(shí)例,就會(huì)報(bào)錯(cuò)提示你在創(chuàng)建@Spy修飾服務(wù)必須要先實(shí)例,此時(shí)只要用@Mock注解替換@Spy就好了。
最后,如果有很多的類都需要做單元測(cè)試,每一個(gè)單元測(cè)試類的頭上都加公共的注解:
@RunWith(SpringRunner.class)
@SpringBootTest
就顯得比較麻煩,可以抽出來寫成一個(gè)Base類,如果Springboot項(xiàng)目有一些公共的配置需要添加也可以放在這個(gè)Base類中:
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class BeseTest {
@BeforeClass
public static void init(){
System.setProperty("server.domain", "test.server.com");
}
}
然后其他單元測(cè)試類使用時(shí)繼承這個(gè)BaseTest類就OK了,不用再每個(gè)類都去加公共的注解、配置:
public class ReplayServiceImplTest extends BestTest
================================================================================================
以上就是本次的全部?jī)?nèi)容,都看到這里了,如果對(duì)你有幫助,麻煩點(diǎn)個(gè)贊+收藏+關(guān)注,一鍵三連啦~文章來源:http://www.zghlxwxcb.cn/news/detail-404513.html
歡迎下方掃碼關(guān)注我的vx公眾號(hào):程序員楊叔,各類文章都會(huì)第一時(shí)間在上面發(fā)布,持續(xù)分享全棧測(cè)試知識(shí)干貨,你的支持就是作者更新最大的動(dòng)力~文章來源地址http://www.zghlxwxcb.cn/news/detail-404513.html
到了這里,關(guān)于Service層代碼單元測(cè)試以及單元測(cè)試如何Mock的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!