专业的JAVA编程教程与资源

网站首页 > java教程 正文

Spock单元测试框架系列(六):Mock静态方法

temp10 2024-12-10 20:47:44 java教程 13 ℃ 0 评论

上一篇中,我们主要介绍了如何使用Spock测试void方法。这一篇中,我们将介绍Spock是如何与PowerMock搭配使用,支持更强大的单元测试的。

在项目开发过程中,我们不可避免的经常会调用一些类的静态方法来完成某些工作。下面就给出一个业务中经常见到的示例,NumberUtilsformatNumber静态方法可以对传入的销量值,按照业务的要求进行格式化操作。

Spock单元测试框架系列(六):Mock静态方法

public class NumberUtils {

    private static final long MILLION = 1000000L;
    private static final long TEN_THOUSAND = 10000L;

    
    public static String formatNumber(long value) {
        // >100万,显示100万+;  > 1万,返回N万+;
        if (value >= MILLION) {
            return "100万+";
        } else if (value >= TEN_THOUSAND) {
            return value / 10000 + "万+";
        } else {
            return String.valueOf(value);
        }
    }
}

我们有一个ItemService类,它有一个方法将ItemDO对象转化成ItemVO对象,其中就用到了 NumberUtils 类的 formatNumber静态方法。

@Service
public class ItemService {

    // 其他业务逻辑
    ...

    public ItemVO convertItemDo2Vo(ItemDO itemDO) {
        ItemVO itemVO = new ItemVO();
        itemVO.setItemId(itemDO.getItemId());
        itemVO.setItemTitle(itemDO.getItemTitle());
        itemVO.setSaleCountDesc(NumberUtils.formatNumber(itemDO.getSaleCount()));
        itemVO.setItemPicture(itemDO.getItemPicture());
        return itemVO;
    }

    // 其他业务逻辑
	...

}

为了仅测试ItemService自身的行为,我们需要将NumberUtilsformatNumber静态方法Mock掉。Spock作为一款groovy 编写的单测框架,仅支持简单的mock,对静态类等的mock也只是对groovy的类起作用,缺乏对java静态类、静态方法等mock的支持,有必要引入其他更强大的单测框架,这就是本文要提到的PowerMock

PowerMock是一款对其他单测框架进行增强的框架。提供了静态类、静态方法、私有方法、构造函数、final变量等的mock方法,可谓功能强大。

引入PowerMock

<dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>3.12.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-core</artifactId>
      <version>2.0.9</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito2</artifactId>
      <version>2.0.9</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>2.0.9</version>
      <scope>test</scope>
    </dependency>

使用

Mock静态方法

引入PowerMock之后,针对上面给出的业务代码示例,我们通过Spock+PowerMock的方式测试。

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([NumberUtils.class])
class ItemServiceTest extends Specification {

    def itemService = new ItemService()

    void setup() {
        // mock 静态类
        PowerMockito.mockStatic(NumberUtils.class)
    }

    @Unroll
    def "test convertItemDo2Vo" () {
        given:
        def itemDO = new ItemDO(
            itemId: 12345L,
            itemTitle: "test",
            saleCount: 100L,
            itemPicture: "test picture",
        )
        and: "mock 静态方法返回"
        PowerMockito.when(NumberUtils.formatNumber(Mockito.anyLong()))
            .thenReturn("100万+")

        when:
    	def itemVO = itemService.convertItemDo2Vo(itemDO)
        then:
        with(itemVO) {
            saleCountDesc == "100万+"
        }
    }
}

Spock的单测代码是继承自Specification基类,而Specification又是基于Junit的注解@RunWith()实现的。

PowerMock的PowerMockRunner也是继承自Junit,所以使用PowerMock的@PowerMockRunnerDelegate()注解可以指定Spock的父类Sputnik去代理运行PowerMock,这样就可以在Spock里使用PowerMock去模拟静态方法、final方法和私有方法等。

@PrepareForTest([NumberUtils.class]) 指定我们将要代理的静态类。然后,在setup()方法里面Mock我们的静态类:PowerMockito.mockStatic(NumberUtils.class)

最后,我们通过 PowerMockito.when(NumberUtils.formatNumber(Mockito.anyLong())).thenReturn("100万+") 指定NumberUtilsformatNumber的返回值为 "100万+"

动态Mock静态方法

使用PowerMock可以让静态方法返回一个指定的值,也是可以每次返回不同的值。我们给上面的业务代码增加一些逻辑,根据上下文环境的不同,给商品打上不同的类型。

@Service
public class ItemService {

    // 其他业务逻辑
    ...

    public ItemVO convertItemDo2Vo(ItemDO itemDO) {
        ItemVO itemVO = new ItemVO();
        itemVO.setItemId(itemDO.getItemId());
        itemVO.setItemTitle(itemDO.getItemTitle());
        itemVO.setSaleCountDesc(NumberUtils.formatNumber(itemDO.getSaleCount()));
        itemVO.setItemPicture(itemDO.getItemPicture());

        // 新增 打标逻辑
        if ("A".equals(ContextUtils.getSource())) {
            itemVO.setType(1);
        } else if ("B".equals(ContextUtils.getSource())) {
            itemVO.setType(2);
        }
        
        return itemVO;
    }
	
    // 其他业务逻辑
	...

}

针对新增逻辑,增加单元测试代码,为了方便的覆盖if-else逻辑,我们Mock ContextUtils.getSource() 静态方法返回不同的值。

Spock的where标签可以方便的和PowerMock结合使用,让PowerMock模拟的静态方法每次返回不同的值。

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([NumberUtils.class])
class ItemServiceTest extends Specification {

    def itemService = new ItemService()

    void setup() {
        // mock 静态类
        PowerMockito.mockStatic(NumberUtils.class)
    }

    @Unroll
    def "test convertItemDo2Vo" () {
        given:
        def itemDO = new ItemDO(
            itemId: 12345L,
            itemTitle: "test",
            saleCount: 100L,
            itemPicture: "test picture",
        )
        and: "mock 静态方法返回"
        PowerMockito.when(NumberUtils.formatNumber(Mockito.anyLong()))
            .thenReturn("100万+")
        PowerMockito.when(ContextUtils.getSource()).thenReturn(currentSource)

        when:
        def itemVO = itemService.convertItemDo2Vo(itemDO)
        then:
        with(itemVO) {
            saleCountDesc == "100万+"
            type == expectedType
        }
        where:
        currentSource || expectedType
        "A"           || 1
        "B"           || 2 
    }
}

PowerMock的thenReturn方法返回的值 是 currentSource 变量,并非具体的值。我们通过where标签来枚举不同的测试用例。

以上代码示例仅为方便演示,都非常简单。实际业务中我们可能会遇到各种各种非常复杂的静态方法,也会有各种复杂的if-else分支条件,通过Spock+PowerMock的结合,让我们可以更加得心应手的应对这些问题,提升我们的单测效率。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表