网站首页 > 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静态方法。
@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自身的行为,我们需要将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可以让静态方法返回一个指定的值,也是可以每次返回不同的值。我们给上面的业务代码增加一些逻辑,根据上下文环境的不同,给商品打上不同的类型。
@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的结合,让我们可以更加得心应手的应对这些问题,提升我们的单测效率。
猜你喜欢
- 2024-12-10 面试官:为什么java中静态方法不能调用非静态方法和变量?
- 2024-12-10 你说你是高工,匿名内部类有我玩得6吗?
- 2024-12-10 详解Java中的静态代理和动态代理
- 2024-12-10 一文讲透父子类中静态变量,成员变量初始化顺序原理
- 2024-12-10 Java错误排行:50种常见bug及如何避免它们(二)
- 2024-12-10 Java中的反射机制:动态调用方法与属性
- 2024-12-10 Java 详细剖析关键字 static,深入全面了解
- 2024-12-10 java静态方法是什么
- 2024-12-10 为何要在Java中使用静态类?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)