前言:
今天咱们对“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静态实例化