3年前 (2021-06-21)  Spock系列 |   抢沙发  1566 
文章评分 0 次,平均分 0.0

如果你在Spock中使用powermock去mock掉jdk类的静态方法,可能会出现powermock不兼容的问题(感谢"有1024个人"这位朋友的反馈,原文见之前这篇文章的评论:https://javakk.com/281.html)。

比如有下面这个要测试的类,里面调用了java.util.Calendar获取当前的年份,其他代码会调用这个方法,我们可能需要对返回的年份进行mock来测试不同的业务逻辑:

public class CalendarUtils {
 
    /**
     * 获取当前年份
     * @return
     */
    public int getYear() {
        return Calendar.getInstance().get(1);
    }
}

一般对java.util.Calendarjava.util.UUIDjava.sql.Date等jdk系统类的静态方法mock,使用powermock实现的话类似下面这种写法:

PowerMockito.mockStatic(java.util.Calendar.class)
PowerMockito.when(java.util.Calendar.getInstance().get(1)).thenReturn(2020) // mock返回年份为2020年

这样的代码在Junit的单元测试里是能运行成功并返回指定的mock值2020,但在Spock中使用可能会出现powermock不兼容的问题,异常类似下面:

org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);. . . . . .

原因是因为powermock的原理是对字节码进行重写,而Spock使用的Groovy语言本身是一种动态语言,他也会对测试类进行一些动态转换,这样的话在针对JDK系统类的重写时可能会有前后顺序不兼容的情况。

不过这种问题仅限于JDK/JRE系统类的mock,我们自己写的静态方法不会受影响。

具体原因可以参考这两个issue:

  • https://github.com/spockframework/spock/issues/1014
  • https://github.com/powermock/powermock/issues/1008

解决方法有两种,一种是修改powermock的用法,另外一种是使用Groovy的@CompileStatic注解

第一种方式(主要是使用PowerMockito.stubmethod方法代替PowerMockito.when方法):

@PrepareForTest([CalendarUtils.class])
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
class MyTest extends Specification {
 
    def cal = new CalendarUtils()
 
    def "testCalendar" () {
        given:
        def calendar = Mock(Calendar)
        PowerMockito.stub(PowerMockito.method(Calendar, "getInstance")).toReturn(calendar) // 使用stub替换when
        calendar.get(_) >> 2020
 
        when:
        def year = cal.getYear()
 
        then:
        year == 2020
    }
}

第二种方式(可以继续使用PowerMockito.when方法,和Junit一样):

import groovy.transform.CompileStatic
......
@PrepareForTest([CalendarUtils.class])
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
class MyTest extends Specification {
 
    def cal = new CalendarUtils()
 
    def "testCalendar" () {
        given:
        mockStatic(2020)
 
        when:
        def year = cal.getYear()
 
        then:
        year == 2020
    }
 
    @CompileStatic
    def mockStatic(int year) {
        PowerMockito.mockStatic(Calendar)
        PowerMockito.when(Calendar.getInstance().get(Mockito.anyInt())).thenReturn(year)
    }
}

注意:@PrepareForTest([CalendarUtils.class]) 参数是你要测试的类(调用Calendar等系统类的类CalendarUtils),而不是Calendar类本身。

这两种写法也都支持Spock的where表格功能。

虽然这个问题解决了,但是写到这里感觉越走越偏了:

正常来说静态方法是不需要被mock的,静态方法本身应该是无状态的、纯计算、不依赖第三方资源配置,无副作用(不会修改全局变量)的函数

但是包括我们自己写的静态方法也存在类似的问题,这也是Spock不去支持静态方法mock的原因。

 

我们写单测的目的到底是为了什么?

  • 仅仅是为了达到测试覆盖率吗?
  • 不管业务代码写的多么不合理,只要单元测试能跑到就行?

单元测试有一个很重要的作用是督促我们写出更合理的代码(高内聚、松耦合、易测试)

如果我们写的业务代码不好测试(不具有可测性),首先考虑的应该是重构

就好像"我没时间磨斧子,因为我忙着砍树"一样,颠倒了主次。

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2038.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册