Java單元測(cè)試Powermockito和Mockito使用總結(jié)
最近公司在推進(jìn)Java應(yīng)用的單元測(cè)試,要求將單元測(cè)試的覆蓋率提高到50%以上,保證上線代碼充分自測(cè)。公司單元測(cè)試框架選用了Junit 4.12,Mock框架選用了Mockito和PowerMock,同時(shí)選用JaCoCo來(lái)做覆蓋率檢測(cè),下面詳細(xì)介紹一下我在使用這幾個(gè)框架的一些經(jīng)驗(yàn)。
依賴引入
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.8.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
PowerMockito的使用
Mockito、EasyMock、JMock等比較流行Mock框架有個(gè)共同的缺點(diǎn),都不能mock靜態(tài)、final、私有方法等,而PowerMock可以完美解決以上框架的不足,接下來(lái)讓我們看看無(wú)所不能的PowerMock是如何解決上述問(wèn)題,我們先從一個(gè)實(shí)例來(lái)看,下面的代碼是需要單測(cè)的類,被測(cè)類中既有對(duì)Spring Bean的調(diào)用,同時(shí)又包含對(duì)Redis的靜態(tài)讀取,另外還有g(shù)etInstance()單例類的使用,現(xiàn)在,我們一一來(lái)mock上述的外部類。
被單測(cè)類(主要測(cè)試queryStudentScoreByKeyword方法)
@Component
public class StudentService {
@Resource
private StudentDao studentDao;
public List<StudentBo> queryStudentScoreByKeyword(String name) {
System.out.println("invoke StudentService.queryStudentScoreByKeyword ...");
List<StudentBo> cacheList = RedisUtils.getArray(name, StudentBo.class);
if (CollectionUtils.isNotEmpty(cacheList)) {
return cacheList;
}
String keyword = processKeyword(name);
List<Student> students = studentDao.queryStudentByKeyWord(keyword);
List<Integer> ids = students.stream().map(Student::getId).collect(Collectors.toList());
List<Person> personList = SchoolManageProxy.getInstance().queryPerson(ids);
List<StudentBo> studentBos = CommonUtils.toBo(personList, students);
// 高亮結(jié)果
highlightResult(studentBos, name);
// 緩存到Redis
RedisUtils.setArray(name, studentBos, 10 * 60);
return studentBos;
}
private String processKeyword(String name) {
System.out.println("invoke StudentService.processKeyword ...");
String newName = name;
// do somethings
return newName;
}
private void highlightResult(List<StudentBo> result, String name) {
System.out.println("invoke StudentService.highlightResult ...");
// do keyword highlight
}
}
單測(cè)類
@RunWith(PowerMockRunner.class)
@PrepareForTest({SchoolManageProxy.class, RedisUtils.class, StudentService.class})
// @PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})
@SuppressStaticInitializationFor({"cn.ganzhiqiang.ares.unittest.SchoolManageProxy"})
public class StudentServiceTest {
@Mock
private StudentDao mockStudentDao;
@InjectMocks
private StudentService studentServiceUnderTest;
@Before
public void setUp() {
initMocks(this);
}
@Test
public void testQueryStudentScoreByKeyword() throws Exception {
studentServiceUnderTest = PowerMockito.spy(studentServiceUnderTest);
PowerMockito.mockStatic(RedisUtils.class);
PowerMockito.mockStatic(SchoolManageProxy.class);
// mock單例調(diào)用
SchoolManageProxy mockSchoolManageProxy = PowerMockito.mock(SchoolManageProxy.class);
PowerMockito.when(SchoolManageProxy.getInstance()).thenReturn(mockSchoolManageProxy);
when(mockSchoolManageProxy.queryPerson(anyList())).thenReturn(Collections.emptyList());
// mock掉對(duì)Redis的靜態(tài)調(diào)用
PowerMockito.when(RedisUtils.getArray(eq("tom"), eq(StudentBo.class))).thenReturn(Collections.emptyList());
// 顯示的mock掉靜態(tài)的void的方法(可以不mock)
PowerMockito.doNothing().when(RedisUtils.class, "setArray", anyString(), anyList(), anyInt());
// mock私有方法processKeyword
PowerMockito.doReturn("tom").when(studentServiceUnderTest, "processKeyword", anyString());
// 跳過(guò)私有方法highlightResult的執(zhí)行
PowerMockito.suppress(PowerMockito.method(StudentService.class, "highlightResult"));
// 使用Mockito來(lái)mock服務(wù)的調(diào)用
when(mockStudentDao.queryStudentByKeyWord(anyString())).thenReturn(Collections.emptyList());
// Run the test
final List<StudentBo> result = studentServiceUnderTest.queryStudentScoreByKeyword("tom");
}
}
使用mockito來(lái)mock實(shí)例
首選我們先用Mockito來(lái)mock對(duì)Spring Bean的調(diào)用,Mockito.mock可以mock一個(gè)實(shí)例,我們這里選用@Mock注解,效果是一樣的。
// 使用Mockito來(lái)mock服務(wù)的調(diào)用 when(mockStudentDao.queryStudentByKeyWord(anyString())).thenReturn(Collections.emptyList());
mock對(duì)Redis的靜態(tài)調(diào)用
接下來(lái)我們使用PowerMock來(lái)mock對(duì)靜態(tài)方法的調(diào)用,注意需要將RedisUtils類,加入@PrepareForTest注解中,我們既mock了getArray方法,也mock了setArray方法,其實(shí)setArray不需要mock這里顯式的mock了
PowerMockito.mockStatic(RedisUtils.class);
// mock掉對(duì)Redis的靜態(tài)調(diào)用
PowerMockito.when(RedisUtils.getArray(eq("tom"), eq(StudentBo.class))).thenReturn(Collections.emptyList());
// 顯式的mock掉靜態(tài)的void的方法(可以不mock)
PowerMockito.doNothing().when(RedisUtils.class, "setArray", anyString(), anyList(), anyInt());
mock單例類
mock單例類相對(duì)來(lái)說(shuō)復(fù)雜一點(diǎn),邏輯上先用Powermock mock出單例類,然后再給單例類的getInstance方法打樁,返回之前mock,再對(duì)mock類實(shí)際調(diào)用的方法打樁即可,代碼如下
PowerMockito.mockStatic(SchoolManageProxy.class); // Powermock mock出單例類 SchoolManageProxy mockSchoolManageProxy = PowerMockito.mock(SchoolManageProxy.class); // 給單例類的getInstance方法打樁 PowerMockito.when(SchoolManageProxy.getInstance()).thenReturn(mockSchoolManageProxy); // 對(duì)mock類queryPerson的方法打樁 when(mockSchoolManageProxy.queryPerson(anyList())).thenReturn(Collections.emptyList());
mock私有方法
可以看到queryStudentScoreByKeyword方法調(diào)用了該類的私有方法processKeyword,如果該方法耗時(shí)過(guò)長(zhǎng),使用powermock也可以mock該私有方法,需要注意的studentServiceUnderTest需要用spy()來(lái)mock
// mock 實(shí)例
// spy的標(biāo)準(zhǔn)是:如果不打樁,默認(rèn)執(zhí)行真實(shí)的方法,如果打樁則返回樁實(shí)現(xiàn)。
studentServiceUnderTest = PowerMockito.spy(studentServiceUnderTest);
// mock私有方法processKeyword
// doReturn(...) when(...)不做真實(shí)調(diào)用,但是when(...) thenReturn(...)還是會(huì)真實(shí)調(diào)用原方法,只是返回了指定的結(jié)果
PowerMockito.doReturn("tom").when(studentServiceUnderTest, "processKeyword", anyString());
PowerMock跳過(guò)方法執(zhí)行
使用PowerMock也可以跳過(guò)私有方法的執(zhí)行
// 跳過(guò)私有方法highlightResult的執(zhí)行 PowerMockito.suppress(PowerMockito.method(StudentService.class, "highlightResult"));
總結(jié)
筆者之前寫(xiě)代碼很少會(huì)寫(xiě)單測(cè),自從公司強(qiáng)制要求提高單測(cè)覆蓋率之后,雖然開(kāi)發(fā)效率變慢了,但是確實(shí)引起我對(duì)單測(cè)的重視,進(jìn)而研究了一下PowerMockito、Mokcito等Mock框架。Powermock之所以無(wú)所不能,是因?yàn)樗褂昧俗远x的加載器和字節(jié)碼操作技術(shù),與此同時(shí),它還十分簡(jiǎn)單易用,確實(shí)是個(gè)很優(yōu)秀的框架。
Demo地址:https://github.com/LJWLgl/mock-data
參考文檔
PowerMock
powermockito單元測(cè)試之深入實(shí)踐
淺談測(cè)試之PowerMock
無(wú)所不能的PowerMock,mock私有方法,靜態(tài)方法,測(cè)試私有方法,final類
Mock和Spy的區(qū)別
到此這篇關(guān)于Java單元測(cè)試Powermockito和Mockito使用總結(jié)的文章就介紹到這了,更多相關(guān)Java Powermockito和Mockito 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)學(xué)生管理系統(tǒng)(IO版)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
使用java?-jar命令啟動(dòng)Spring?Boot應(yīng)用時(shí)指定特定配置文件的幾種實(shí)現(xiàn)方式
這篇文章主要介紹了在使用java-jar命令啟動(dòng)SpringBoot應(yīng)用時(shí),指定特定配置文件的幾種方式,文中通過(guò)代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2025-01-01
Java中的concurrenthashmap集合詳細(xì)剖析
這篇文章主要介紹了Java中的concurrenthashmap集合詳細(xì)剖析,有參構(gòu)造后第一次put時(shí)會(huì)進(jìn)行初始化,由源碼可知,會(huì)先判斷所傳入的容量是否>=最大容量的一半,如果滿足條件,就會(huì)將容量修改為最大值,反之則會(huì)將容量改為所傳入數(shù)*1.5+1,需要的朋友可以參考下2023-11-11
SpringBoot實(shí)現(xiàn)讀取YML,yaml,properties文件
yml,yaml,properties三種文件都是用來(lái)存放配置的文件,一些靜態(tài)數(shù)據(jù),配置的數(shù)據(jù)都會(huì)存放到里邊。本文主要為大家整理了SpringBoot實(shí)現(xiàn)讀取YML,yaml,properties文件的方法,需要的可以參考一下2023-04-04
使用spring boot通過(guò)自定義注解打印所需日志
這篇文章主要介紹了使用spring boot通過(guò)自定義注解打印所需日志的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
SpringBoot+kaptcha實(shí)現(xiàn)驗(yàn)證碼花式玩法詳解
這篇文章主要想和大家聊聊kaptcha的用法,畢竟這個(gè)已經(jīng)有16年歷史的玩意還在有人用,說(shuō)明它的功能還是相當(dāng)強(qiáng)大的,感興趣的小伙伴可以了解一下2022-05-05

