龙空技术网

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

seikeicy 111

前言:

今天咱们对“java静态实例化”大概比较关切,朋友们都想要学习一些“java静态实例化”的相关知识。那么小编也在网上汇集了一些关于“java静态实例化””的相关知识,希望我们能喜欢,兄弟们快快来了解一下吧!

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

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

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静态方法。

@Servicepublic 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自身的行为,我们需要将NumberUtils的formatNumber静态方法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万+") 指定NumberUtils的formatNumber的返回值为 "100万+"

动态Mock静态方法

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

@Servicepublic 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的结合,让我们可以更加得心应手的应对这些问题,提升我们的单测效率。

标签: #java静态实例化