Spock單元測試框架實戰指南六 - 靜態方法測試


本篇主要講解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加載,加載的靜態方法越多會相應的增加測試時長

文章來源:http://javakk.com/302.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM