本篇主要講解Spock如何擴展第三方Power Mock對靜態方法進行測試
實現原理
前面的文章講到Spock的單測代碼是繼承自Specification
基類,而Specification
又是基於Junit的注解@RunWith()
實現的,代碼如下:
@RunWith(Sputnik.class)
@SuppressWarnings("UnusedDeclaration")
public abstract class Specification extends MockingApi {
powermock的PowerMockRunner
也是繼承自Junit,所以使用powermock的@PowerMockRunnerDelegate()
注解可以指定Spock的父類Sputnik
去代理運行power mock,這樣就可以在Spock里使用powermock去模擬靜態方法、final
方法、私有方法等
其實Spock自帶的GroovyMock
可以對groovy文件的靜態方法mock,但對Java代碼的支持不完整,只能mock當前Java類的靜態方法,官方給出的解釋如下:
(http://spockframework.org/spock/docs/1.3/all_in_one.html#_mocking_static_methods)
因為我們項目中存在很多調用靜態方法的代碼,現階段考慮重構業務代碼的成本過高,所以這里使用擴展power mock的方式測試靜態方法
Spock 代理 Power Mock
先看下需要測試的業務代碼示例:
public UserVO getUserByIdStatic(int uid){
List<UserDTO> users = userDao.getUserInfo();
UserDTO userDTO = users.stream().filter(u -> u.getId() == uid).findFirst().orElse(null);
UserVO userVO = new UserVO();
if(null == userDTO){
return userVO;
}
userVO.setId(userDTO.getId());
userVO.setName(userDTO.getName());
userVO.setSex(userDTO.getSex());
if("上海".equals(userDTO.getProvince())){
userVO.setAbbreviation("滬");
userVO.setPostCode(200000);
}
if("北京".equals(userDTO.getProvince())){
userVO.setAbbreviation("京");
userVO.setPostCode(100000);
}
if(null != userDTO.getTelephone() && !"".equals(userDTO.getTelephone())){
userVO.setTelephone(userDTO.getTelephone().substring(0,3)+"****"+userDTO.getTelephone().substring(7));
}
// 靜態方法調用 身份證工具類
Map<String, String> idMap = IDNumberUtils.getBirAgeSex(userDTO.getIdNo());
userVO.setAge(idMap.get("age")!=null ? Integer.parseInt(idMap.get("age")) : 0);
// 靜態方法調用 記錄日志
LogUtils.info("response user:", userVO.toString());
return userVO;
}
在倒數第4行和倒數第2行代碼分別調用了 "身份證工具類IDNumberUtils.getBirAgeSex()
" 和 "LogUtils.info()
" 日志記錄的方法,如果要對這兩個靜態方法進行mock,我們可以使用Spock+power mock的方式:
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.spockframework.runtime.Sputnik
import spock.lang.Specification
/**
* 測試靜態方法mock
* @Author: www.javakk.com
* @Description: 公眾號:Java老K
* @Date: Created in 20:53 2020/7/16
* @Modified By:
*/
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([LogUtils.class, IDNumberUtils.class])
@SuppressStaticInitializationFor(["com.javakk.spock.util.LogUtils"])
class UserServiceStaticTest extends Specification {
def processor = new UserService()
def dao = Mock(UserDao)
void setup() {
processor.userDao = dao
// mock靜態類
PowerMockito.mockStatic(LogUtils.class)
PowerMockito.mockStatic(IDNumberUtils.class)
}
def "GetUserByIdStatic"() {
given: "設置請求參數"
def user1 = new UserDTO(id:1, name:"張三", province: "上海")
def user2 = new UserDTO(id:2, name:"李四", province: "江蘇")
def idMap = ["birthday": "1992-09-18", "sex": "男", "age": "28"]
and: "mock掉接口返回的用戶信息"
dao.getUserInfo() >> [user1, user2]
and: "mock靜態方法返回值"
PowerMockito.when(IDNumberUtils.getBirAgeSex(Mockito.any())).thenReturn(idMap)
when: "調用獲取用戶信息方法"
def response = processor.getUserByIdStatic(1)
then: "驗證返回結果是否符合預期值"
with(response) {
name == "張三"
abbreviation == "滬"
postCode == 200000
age == 28
}
}
}
在UserServiceStaticTest
類的頭部使用@PowerMockRunnerDelegate(Sputnik.class)
注解,交給Spock代理執行,這樣既可以使用 Spock + groovy 的各種功能,又可以使用power mock的對靜態,final等方法的mock
@SuppressStaticInitializationFor(["com.javakk.spock.util.LogUtils"])
這行代碼的作用是限制LogUtils
類里的靜態代碼塊初始化,因為LogUtils
類在第一次調用時會加載一些本地資源配置,比較耗費時間,所以可以使用power mock禁止初始化
然后在setup()
方法里對兩個靜態類進行mock設置
PowerMockito.mockStatic(LogUtils.class),PowerMockito.mockStatic(IDNumberUtils.class)
最后在GetUserByIdStatic
測試方法里對getBirAgeSex()
方法指定返回默認值:PowerMockito.when(IDNumberUtils.getBirAgeSex(Mockito.any())).thenReturn(idMap)
(power mock的具體用法網上資料很多,這里不展開說明)
運行時在控制台會輸出:
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
這是powermock的警告信息,不影響運行結果
另外,如果你的單元測試代碼不需要對靜態方法, final方法mock, 就沒必要使用power mock, 使用Spock自帶的Mock()就足夠了
因為power mock的原理是在編譯期通過ASM字節碼修改工具修改我們的代碼,然后使用自己的classLoader
加載,加載的靜態方法越多會相應的增加測試時長