PowerMock(一):PowerMock的基本使用
文章目錄
為啥要使用PowerMock
PowerMock的使用
環(huán)境
引入依賴
注解說明
mock普通方法
mock拋出異常
mock新建對(duì)象
mock無返回值的方法
mock被final修飾的方法
參數(shù)模糊匹配
mock靜態(tài)方法
mock私有方法
總結(jié)
參考
為啥要使用PowerMock
現(xiàn)在流行的測試驅(qū)動(dòng)開發(fā)TDD(Test-Driven Development) ,是敏捷開發(fā)中一項(xiàng)核心實(shí)踐和技術(shù)。也是一種設(shè)計(jì)方法論。其中最重要的一環(huán)就是使用單元測試。
單元測試是保證代碼質(zhì)量的一個(gè)重要手段,通過單元測試我們可以快速的測試代碼的各個(gè)分支,各種場景,代碼重構(gòu)時(shí)只需要重新跑下單元測試就是能知道代碼潛在的問題。
單元測試是通過Mock的方式調(diào)用被測試的方法,其有如下幾個(gè)優(yōu)點(diǎn):
Mock可以解除測試對(duì)象對(duì)外部服務(wù)的依賴(比如數(shù)據(jù)庫,第三方接口等),使得測試用例可以獨(dú)立運(yùn)行。不管是單體應(yīng)用還是微服務(wù),這點(diǎn)都特別重要。
Mock的第二個(gè)好處就是替換外部服務(wù)調(diào)用,提升測試用例的運(yùn)行速度。因?yàn)槿魏瓮獠糠?wù)調(diào)用至少是跨進(jìn)程級(jí)別的消耗,甚至是跨系統(tǒng)、跨網(wǎng)絡(luò)的消耗,而Mock可以把消耗降低到進(jìn)程內(nèi)。
Mock的第三個(gè)好處就是提升測試效率,提高單位時(shí)間內(nèi)測試的接口數(shù)量。
Mock的框架有很多中比如EasyMock等,這里選用PowerMock是因?yàn)镻owerMock可以用來Mock 私有方法,靜態(tài)方法以及final方法。EasyMock等則不能。
PowerMock的使用
環(huán)境
軟件 版本
junit 4.13
powermock 2.0.7
引入依賴
<properties>
<powermock.version>2.0.7</powermock.version>
</properties>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!--powermock開始-->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<!--powermock結(jié)束-->
這里引入了是三個(gè)依賴,junit依賴如果項(xiàng)目中已有的話,則不需要重復(fù)引入,需要注意的是JUnit 4.4及以上版本的JUnit需要引入2.0.x 版本以上的 powermock 。如果項(xiàng)目中有mockito依賴還需要注意mockito的版本與powermock版本對(duì)應(yīng)關(guān)系,對(duì)應(yīng)如下圖:
詳細(xì)請(qǐng)參考Using PowerMock with Mockito,如果引入的版本不匹配則可能會(huì)報(bào)如下錯(cuò)誤:
java.lang.TypeNotPresentException: Type org.powermock.modules.junit4.PowerMockRunner not present
依賴引入之后就可以編寫單元測試代碼了。
注解說明
現(xiàn)有一個(gè)待測試的類UserServiceImpl,該類中注入了一個(gè)UserMapper的類實(shí)例。
@Service
public class UserServiceImpl {
@Autowried
private UserMapper userMapper;
........省略部分方法
}
那么如何對(duì)上面的類通過powermock的方式進(jìn)行單元測試呢?首先是定義一個(gè)測試類,定義如下:
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserServiceImpl.class, DateUtil.class, UserMapper.class})
public class UserServiceImplTest {
@Mock
private UserMapper userMapper;
@InjectMocks
private UserServiceImpl userServiceImpl = new UserServiceImpl();
}
@RunWith(PowerMockRunner.class) 注解表明使用PowerMockRunner運(yùn)行測試用例,這個(gè)必須添加,不然無法使用PowerMock。
@PrepareForTest({UserServiceImpl.class, DateUtil.class, UserMapper.class}) @PrepareForTest 注解是用來添加所有需要測試的類,這里列舉了三個(gè)需要測試的類。
@Mock注解修飾會(huì)mock出來一個(gè)對(duì)象,這里mock出來的是UserMapper類實(shí)例。
@InjectMocks 注解會(huì)主動(dòng)將已存在的mock對(duì)象注入到bean中,按名稱注入,這個(gè)注解修飾在我們需要測試的類上。必須要手動(dòng)new一個(gè)實(shí)例,不然單元測試會(huì)有問題。
這幾個(gè)注解是一個(gè)測試類必須要的。說完了測試類的定義,接下來就讓我們來看看各種方法是如何mock的。
mock普通方法
待測試的方法(UserMapper中)
boolean saveUser(User user) {
int i = userMapper.addUser(user);
return i == 1 ? true : false;
}
這里的方法int i = userMapper.addUser(user); 有入?yún)?,有出參,沒有關(guān)鍵字修飾,是一個(gè)普通的方法,mock的方式也很簡單,就是PowerMockito.when(userMapper.addUser(user)).thenReturn(1); 在when方法中調(diào)用你需要mock的方法,thenReturn方法寫入你期待返回的值。從字面意思理解就是當(dāng)調(diào)用xxx方法時(shí),返回xxx值。 詳細(xì)的示例如下:
2. 測試方法
User user = new User();
user.setId(1);
user.setUserName("test");
user.setPassword("admin123");
PowerMockito.when(userMapper.addUser(user)).thenReturn(1);
boolean result = userServiceImpl.saveUser(user);
Assert.assertEquals(true, result);
mock拋出異常
單元測試中我們有時(shí)候需要mock異常的拋出,其mock的方式也很簡單就是在thenThrow(new Exception())寫入你期待拋出的異常。如果被mock的方法拋出的是受檢異常(checked exception)的話,那么thenThrow拋出new Exception()或者其子類。
如果被mock的方法拋出的是非受檢異常(unchecked exception),那么thenThrow拋出new RuntimeException或其子類。使用的示范如下:
待測試的方法(UserMapper中)
int delUser(int id) throws Exception {
if (id == -1) {
throw new Exception("傳入的id值不對(duì)");
} else {
return 1;
}
}
測試方法
PowerMockito.when(userMapper.delUser(-1)).thenThrow(new Exception());
這里delUser方法拋出的是受檢異常Exception,所以在thenThrow中需要new一個(gè)Exception對(duì)象。
mock新建對(duì)象
如果我們要對(duì)一個(gè)實(shí)體對(duì)象Bean進(jìn)行Mock,只需要這樣寫PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user)
這個(gè)代碼的意思是創(chuàng)建一個(gè)User實(shí)例對(duì)象,不管傳入啥參數(shù)都返回定義的實(shí)例user,用于替換被測試方法中相應(yīng)的User對(duì)象。使用示范如下:
待測試的方法(UserMapper中)
public int countUser() {
User user = new User();
int count = 0;
if (user.getId() > 0) {
count += 1;
}
return count;
}
測試方法
// 6.mock新建對(duì)象
User user = new User();
user.setId(11);
PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user);
int result = userServiceImpl.countUser();
Assert.assertEquals(1, result);
這里mock了一個(gè)User對(duì)象。id是11,當(dāng)調(diào)用countUser方法時(shí)可以拿到之前mock的User對(duì)象,所以返回的結(jié)果是1。
mock無返回值的方法
對(duì)于返回值是通過void修飾的方法,他的mock方式與普通方法的mock方式不同。有兩種方式mock。
方式一:
PowerMockito.doNothing().when(userMapper, "updateUser", new User());
在when方法中傳入userMapper類實(shí)例,需要調(diào)用的方法名,以及需要傳入的參數(shù)。
方式二:
PowerMockito.doNothing().when(userMapper).updateUser(user);
在when方法中只傳入userMapper類實(shí)例,然后通過函數(shù)式調(diào)用的方式調(diào)用待測試的方法。
使用示范如下:
待測試的方法(UserServiceImpl中)
public void updateUser(User user) {
userMapper.updateUser(user);
}
測試方法
User user = new User();
// 4.mock返回值為void的方法
//方法一
PowerMockito.doNothing().when(userMapper, "updateUser", new User());
//方法二
PowerMockito.doNothing().when(userMapper).updateUser(user);
userServiceImpl.updateUser(user);
mock被final修飾的方法
現(xiàn)在有一個(gè)方法被final關(guān)鍵字修飾,那么該如何要mock這個(gè)方法,首先需要mock出一個(gè)類實(shí)例。如下所示:
UserMapper mock = PowerMockito.mock(UserMapper.class);
這里需要特別注意的是被mock的類必須要在@PrepareForTest注解中指定,如本例中的@PrepareForTest({UserMapper.class})。不然就會(huì)報(bào)如下錯(cuò)誤:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
使用示范如下:
待測試的方法(UserMapper中)
final String getUserName() {
return "admin";
}
測試方法
UserMapper mock = PowerMockito.mock(UserMapper.class);
when(mock.getUserName()).thenReturn("123");
參數(shù)模糊匹配
前面的測試方法中,參數(shù)我們都是指定的,在一些場景下,對(duì)于一些比較復(fù)雜的參數(shù),我們不好構(gòu)造,這時(shí)候參數(shù)模糊匹配就派上用場了。如下所示,現(xiàn)有方法selectUser,他有三個(gè)參數(shù),參數(shù)類型個(gè)不相同。
User selectUser(Integer id, String userName, String password)
當(dāng)對(duì)這個(gè)方法進(jìn)行mock時(shí),可以不用傳入具體的參數(shù)值。就行這樣進(jìn)行mock。
PowerMockito.when(userMapper.selectUser(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(user);
其中ArgumentMatchers.anyInt()是指任意的int類型的值,ArgumentMatchers.anyString()是指任意String類型的值。需要特別注意的是一個(gè)方法中只要有一個(gè)參數(shù)使用了模糊匹配,其余的參數(shù)也都需要使用模糊匹配。
mock靜態(tài)方法
對(duì)靜態(tài)方法的mock也比較簡單,與普通方法的mock相比只是多了一行代碼。就是首先需要對(duì)靜態(tài)方法的所在的類進(jìn)行mock。
PowerMockito.mockStatic(DateUtil.class);
同時(shí)被mock的類必須要在@PrepareForTest注解中指定,像本例中的DateUtil類。@PrepareForTest({ DateUtil.class}),其他的與普通方法的mock一樣,再此就不在贅述了。
mock私有方法
當(dāng)我們需要測試的方法中調(diào)用了一個(gè)比較復(fù)雜的私有方法時(shí),我們該如何mock呢?針對(duì)這種情況PowerMock也可以輕松應(yīng)對(duì)。首先調(diào)用spy方法創(chuàng)建出一個(gè)新的UserServiceImpl類實(shí)例。然后通過這個(gè)實(shí)例來mock這個(gè)私有方法。如下所示:
UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
PowerMockito.when(spy, "verifyId", 0).thenReturn(true);
使用示范:
待測試的方法
boolean delUser(int id) throws Exception {
int i = userMapper.delUser(id);
return verifyId(i);
}
測試方法
public void testVerifyId() throws Exception {
// 5.mock私有方法
UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
PowerMockito.when(spy, "verifyId", 0).thenReturn(true);
PowerMockito.when(userMapper.delUser(0)).thenReturn(1);
Assert.assertEquals(userServiceImpl.delUser(0), true);
}
總結(jié)
本文詳細(xì)介紹了PowerMock的常見使用,PowerMock是一個(gè)應(yīng)用比較廣泛的單元測試框架,運(yùn)用在單元測試中可以很好的提供測試效率。PowerMock可以mock 普通方法,私有方法,靜態(tài)方法,final修飾的方法。
參考
無所不能的PowerMock,mock私有方法,靜態(tài)方法,測試私有方法,final類
power mock 入門介紹及使用示例
作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥