详解Java 中的UnitTest 和 PowerMock
学习一门计算机语言,我觉得除了学习它的语法外,最重要的就是要学习怎么在这个语言环境下进行单元测试,因为单元测试能帮你提早发现错误;同时给你的程序加一道防护网,防止你的修改破坏了原有的功能;单元测试还能指引你写出更好的代码,毕竟不能被测试的代码一定不是好代码;除此之外,它还能增加你的自信,能勇敢的说出「我的程序没有bug」。
每个语言都有其常用的单元测试框架,本文主要介绍在Java中,我们如何使用PowerMock,来解决我们在写单元测试时遇到的问题,从Mock这个词可以看出,这类问题主要是解依赖问题。
在写单元测试时,为了让测试工作更简单、减少外部的不确定性,我们一般都会把被测类和其他依赖类进行隔离,不然你的类依赖得越多,你需要做的准备工作就越复杂,尤其是当它依赖网络或外部数据库时,会给测试带来极大的不确定性,而我们的单测一定要满足快速、可重复执行的要求,所以隔离或解依赖是必不可少的步骤。
而Java中的PowerMock库是一个非常强大的解依赖库,下面谈到的3个特性,可以帮你解决绝大多数问题:
1通过PowerMock注入依赖对象
2利用PowerMock来mockstatic函数
3输出参数(outputparameter)怎么mock
通过PowerMock注入依赖对象
假设你有两个类,MyService和MyDao,MyService依赖于MyDao,且它们的定义如下
//MyDao.java
@Mapper
publicinterfaceMyDao{
/**
*根据用户id查看他最近一次操作的时间
*/
DategetLastOperationTime(longuserId);
}
//MyService.java
@Service
publicclassMyService{
@Autowired
privateMyDaomyDao;
publicbooleanoperate(longuserId,Stringoperation){
DatelastTime=myDao.getLastOperationTime(userId);
//...
}
}
这个服务提供一个operate接口,用户在调用该接口时,会被限制一个操作频次,所以系统会记录每个用户上次操作的时间,通过MyDao.getLastOperationTime(longuserId)接口获取,现在我们要对MyService类的operate做单元测试,该怎么做?
你可能会想到使用SpringBoot,它能自动帮我们初始化myDao对象,但这样做却存在一些问题:
1SpringBoot的启动速度很慢,这会延长单元测试的时间
2因为时间是一个不断变化的量,也许这一次你构造的时间满足测试条件,但下一次运行测试时,可能就不满足了。
由于以上原因,我们一般在做单元测试时,不启动SpringBoot上下文,而是采用PowerMock帮我们注入依赖,对于上面的case,我们的测试用例可以这样写:
//MyServiceTest.java
@RunWith(PowerMockRunner.class)
@PrepareForTest({MyService.class,MyDao.class})
publicclassMyServiceTest{
@Test
publicvoidtestOperate()throwsIllegalAccessException{
//构造一个和当前调用时间永远只差4秒的返回值
Calendarcalendar=Calendar.getInstance();
calendar.add(Calendar.SECOND,-4);
DateretTime=calendar.getTime();
//spy是对象的“部分mock”
MyServicemyService=PowerMockito.spy(newMyService());
MyDaomd=PowerMockito.mock(MyDao.class);
PowerMockito
.when(md.getLastOperationTime(Mockito.any(long.class)))
.thenReturn(retTime);
//替换myDao成员
MemberModifier.field(MyService.class,"myDao").set(myService,md);
//假设最小操作的间隔是5秒,否则返回false
Assert.assertFalse(myService.operate(1,"testoperation"));
}
}
从上面代码中,我们首先构造了一个返回时间retTime,模拟操作间隔的时间为4秒,保证了每次运行测试时该条件不会变化;然后我们用spy构造一个待测试的MyService对象,spy和mock的区别是,spy只会部分模拟对象,即这里只修改掉myService.myDao成员,其他的保持不变。
然后我们定义了被mock的对象MyDaomd的调用行为,当md.getLastOperationTime函数被调用时,返回我们构造的时间retTime,此时测试环境就设置完毕了,这样做之后,你就可以很容易的测试operate函数了。
利用PowerMock来mockstatic函数
上文所说的使用PowerMock进行依赖注入,可以覆盖测试中绝大多数的解依赖场景,而另一种常见的依赖是static函数,例如我们自己写的一些CommonUtil工具类中的函数。
还是使用上面的例子,假设我们要计算当前时间和用户上一次操作时间之间的间隔,并使用publicstaticlonggetTimeInterval(DatelastTime)实现该功能,如下:
//CommonUtil.java
classCommonUtil{
publicstaticlonggetTimeInterval(DatelastTime){
longduration=Duration.between(lastTime.toInstant(),
newDate().toInstant()).getSeconds();
returnduration;
}
}
我们的operator函数修改如下
//MyService.java
//...
publicbooleanoperate(longuserId,Stringoperation){
DatelastTime=myDao.getLastOperationTime(userId);
longduration=CommonUtil.getTimeInterval(lastTime);
if(duration>=5){
System.out.println("user:"+userId+""+operation);
returntrue;
}else{
returnfalse;
}
}
//...
这里先从myDao获取上次操作的时间,再调用CommonUtil.getTimeInterval计算操作间隔,如果小于5秒,就返回false,否则执行操作,并返回true。那么我的问题是,如何解掉这里static函数的依赖呢?我们直接看测试代码吧
//MyServiceTest.java
@PrepareForTest({MyService.class,MyDao.class,CommonUtil.class})
publicclassMyServiceTest{
//...
@Test
publicvoidtestOperateWithStatic()throwsIllegalAccessException{
//...
PowerMockito.spy(CommonUtil.class);
PowerMockito.doReturn(5L).when(CommonUtil.class);
CommonUtil.getTimeInterval(Mockito.anyObject());
//...
}
}
首先在注解@PrepareForTest中增加CommonUtil.class,依然使用spy对类CommonUtil进行mock,如果不这么做,这个类中所有静态函数的行为都会发生变化,这会给你的测试带来麻烦。spy下面的两行代码你应该放在一起解读,意为当调用CommonUtil.getTimeInterval时,返回5;这种写法比较奇怪,但却是PowerMock要求的。至此,你已经掌握了mockstatic函数的技巧。
输出参数(outputparameter)怎么mock
有些函数会通过修改参数所引用的对象作为输出,例如下面的这个场景,假设我们的operation是一个长时间执行的任务,我们需要不断轮训该任务的状态,更新到内存,并对外提供查询接口,如下代码:到内存,并对外提供查询接口,如下代码:
//MyTask.java
//...
publicbooleanrun()throwsInterruptedException{
while(true){
updateStatus(operation);
if(operation.getStatus().equals("success")){
returntrue;
}else{
Thread.sleep(1000);
}
}
}
publicvoidupdateStatus(Operationoperation){
Stringstatus=myDao.getStatus(operation.getOperationId());
operation.setStatus(status);
}
//...
上面的代码中,run()是一个轮询任务,它会不断更新operation的状态,并在状态达到"success"时停止,可以看到,updateStatus就是我们所说的函数,虽然它没有返回值,但它会修改参数所引用的对象,所以这种参数也被称作输出参数。
现在我们要测试run()函数的行为,看它是否会在"success"状态下退出,那么我们就需要mockupdateStatus函数,该怎么做?下面是它的测试代码:
@Test
publicvoidtestUpdateStatus()throwsInterruptedException{
//初始化被测对象
MyTaskmyTask=PowerMockito.spy(newMyTask());
myTask.setOperation(newMyTask.Operation());
//使用doAnswer来mockupdateStatus函数的行为
PowerMockito.doAnswer(newAnswer
上面的代码中,我们使用doAnswer来mockupdateStatus的行为,相当于使用answer函数来替换原来的updateStatus函数,在这里,我们将operation的状态设置为了"success",以期待myTask.run()函数返回true。于是,我们又学会了如何mock具有输出参数的函数了。
以上就是详解Java中的UnitTest和PowerMock的详细内容,更多关于JavaUnitTest和PowerMock的资料请关注毛票票其它相关文章!