目录
1 单元测试
自己认为,单元测试最重要的作用有如下两点
①开发人员实现某个功能或者修补了某个bug,如果有相应的单元测试支持的话,开发人员可以马上通过运行单元测试来验证之前完成的代码是否正确,而不需要反复通过发布war包、启动jboss、通过浏览器输入数据等繁琐的步骤来验证所完成的功能
②保证你最后的代码修改不会破坏之前代码的功能。项目越做越大,代码越来越多,特别涉及到一些公用接口之类的代码或是底层的基础库,谁也不敢保证这次修改的代码不会破坏之前的功能,所以与此相关的需求会被搁置或推迟,由于不敢改进代码,代码也变得越来越难以维护,质量也越来越差。而单元测试就是解决这种问题的很好方法(不敢说最好的)。由于代码的历史功能都有相应的单元测试保证,修改了某些代码以后,通过运行相关的单元测试就可以验证出新调整的功能是否有影响到之前的功能。当然要实现到这种程度需要很大的付出,不但要能够达到比较高的测试覆盖率,而且单元测试代码的编写质量也要有保证
2 Junit测试框架
2.1 Junit是什么
JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
注意:Junit 测试也是程序员测试,即所谓的白盒测试,它需要程序员知道被测试的代码如何完成功能,以及完成什么样的功能
2.2 Junit 能做什么?
我们知道 Junit 是一个单元测试框架,那么使用 Junit 能让我们快速的完成单元测试。
通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,然后编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方法来测试,要么将其全部写在一个 main() 方法里面。这也会大大的增加测试的复杂度,降低程序员的测试积极性。而 Junit 能很好的解决这个问题,简化单元测试,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。
3 Junit测试的局限性
1、在某些非常复杂的业务逻辑,会准备大量的数据。
2、有的时候会依赖数据库,中间件、文件系统等外部环境,这个时候我们不能控制这些外部依赖的对象。
试想一下,如果我们依赖真实的数据库环境,那么每次的单元测试结果可能都是不一样的
为了解决上述两个问题,我们需要使用Mock技术
4 Mock技术
截取一段stackflow中的解释:
Mocking isprimarily used in unit testing. An object under test may have dependencies onother (complex) objects. To isolate the behaviour of the object you want totest you replace the other objects by mocks that simulate the behavior of thereal objects. This is useful if the real objects are impractical to incorporateinto the unit test.
MOCK主要被用于在单测中,某个对象在测试过程中有可能依赖于其他的复杂对象,通过mocks去模拟真实的其他对象(模块)去代替你想要去测试的其他的对象(模块),如果其他对象(模块)是很难从单元测试中剥离开来的话,这是非常有用的
Mock有以下几个好处:
1、Mock可以用来解除测试对象对外部服务的依赖(比如数据库,第三方接口等),使得测试用例可以独立运行。不管是传统的单体应用,还是现在流行的微服务,这点都特别重要,因为任何外部依赖的存在都会极大的限制测试用例的可迁移性和稳定性。可迁移性是指,如果要在一个新的测试环境中运行相同的测试用例,那么除了要保证测试对象自身能够正常运行,还要保证所有依赖的外部服务也能够被正常调用。稳定性是指,如果外部服务不可用,那么测试用例也可能会失败。通过Mock去除外部依赖之后,不管是测试用例的可迁移性还是稳定性,都能够上一个台阶。
2、Mock的第二个好处是替换外部服务调用,提升测试用例的运行速度。任何外部服务调用至少是跨进程级别的消耗,甚至是跨系统、跨网络的消耗,而Mock可以把消耗降低到进程内。比如原来一次秒级的网络请求,通过Mock可以降至毫秒级,整整3个数量级的差别
3、Mock的第三个好处是提升测试效率。这里说的测试效率有两层含义。第一层含义是单位时间运行的测试用例数,这是运行速度提升带来的直接好处。而第二层含义是一个测试人员单位时间创建的测试用例数。如何理解这第二层含义呢?以单体应用为例,随着业务复杂度的上升,为了运行一个测试用例可能需要准备很多测试数据,与此同时还要尽量保证多个测试用例之间的测试数据互不干扰。为了做到这一点,测试人员往往需要花费大量的时间来维护一套可运行的测试数据。有了Mock之后,由于去除了测试用例之间共享的数据库依赖,测试人员就可以针对每一个或者每一组测试用例设计一套独立的测试数据,从而很容易的做到不同测试用例之间的数据隔离性。而对于微服务,由于一个微服务可能级联依赖很多其他的微服务,运行一个测试用例甚至需要跨系统准备一套测试数据,如果没有Mock,基本上可以说是不可能的。因此,不管是单体应用还是微服务,有了Mock之后,QE就可以省去大量的准备测试数据的时间,专注于测试用例本身,自然也就提升了单人的测试效率。
5 相关的Mock工具
5.1 Mockito、EasyMock
EasyMock 以及 Mockito 都因为可以极大地简化单元测试的书写过程而被许多人应用在自己的工作中,但是这两种 Mock 工具都不可以实现对静态函数、构造函数、私有函数、Final 函数以及系统函数的模拟,但是这些方法往往是我们在大型系统中需要的功能。
5.2 powermock
PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。通过使用自定义的类加载器,简化采用的IDE或持续集成服务器不需要做任何改变。熟悉PowerMock支持的mock框架的开发人员会发现PowerMock很容易使用,因为对于静态方法和构造器来说,整个的期望API是一样的。PowerMock旨在用少量的方法和注解扩展现有的API来实现额外的功能。目前PowerMock支持EasyMock和Mockito。
5.3 mock底层原理
①Mockito底层使用了动态代理,用到了CGLIB。因此需要被mock的对象,Mockito都会生成一个子类继承该类,这也就是为什么final类、private方法、static方法不可以被Mock的原因
②powermock的底层原理
我们首先看powermock的依赖
可以看出来,它有两个重要的依赖:javassist和objenesis。
javassist是一个修改java字节码的工具包,objenesis是一个绕过构造方法来实例化一个对象的工具包。由此看来,PowerMock的本质是通过修改字节码来实现对静态和final等方法的mock的
下面是PowerMock的简单实现原理:
- 当某个测试方法被注解@PrepareForTest标注以后,在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,然后加载该测试用例使用到的类(系统类除外)。
- PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。
- 如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。
6 powermock的使用
这里吐血推荐一本电子书
网盘链接: https://pan.baidu.com/s/1ZndwumRgSTqmtn__RNVosA 密码:e8w4
7 springboot和powermock整合
一定要注意powermock的版本号,我在这里就踩了很多坑,血淋淋的教训啊,由于公司与springboot继承用的是最新的powermock,截止2019年4月12日,powermock-module-junit4的Maven地址仓库最新版本是2.0.0 ,也正是powermock使用的太新了,导致后面遇到的问题,百度google根本无法解决(因为他们都还停留在1.x版本中),最后也是通过github官方文档才最终解决的
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
7.1 重要注解
@SpringBootTest // 表明这是一个springboot测试类,会自动加载springboot主启动程序
@RunWith(PowerMockRunner.class) //使用powermock自己的Runner
@PowerMockRunnerDelegate(SpringRunner.class) //将powermock整合到spring容器中
@PowerMockIgnore({"javax.*.*", "com.sun.*", "org.xml.*", "org.apache.*"})
@PrepareForTest({HSSFWorkbook.class,HSSFCellStyle.class})
public class Demo {
@Test
public void test() throws Exception {
EmployeeService service = PowerMockito.mock(EmployeeService.class);
PowerMockito.when(service.hello()).thenReturn(999);
int result = service.hello();
Assert.assertEquals(999, result);
}
}
下面主要对上面几个注解做相关解释:
@SpringBootTest:表明这是一个springboot测试类,会自动加载springboot主启动程序
@RunWith(PowerMockRunner.class): 使用powermock自己的Runner
@PowerMockRunnerDelegate(SpringRunner.class): 将powermock整合到spring容器中
@PowerMockIgnore({"javax.*
.*
", "com.sun.", "org.xml.", "org.apache.*"}) : 这个注解很重要,这也是powermock2.0.0与1.x版本重大不一样的地方,因为powermock自带一个类加载器,使用该注解来禁止powermock类加载器加载一些类,避免和JVM类加载器冲突
@PrepareForTest({HSSFWorkbook.class,HSSFCellStyle.class}): 这个注解是告诉PowerMock为我提前准备一个xxx的class,根据我测试预期的行为去准备
至此,springboot和powermock的整合就完成了!
7.2 PrepareForTest不能随便加
首先来看一段代码:不使用@PrepareForTest
@SpringBootTest
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PowerMockIgnore({"javax.*.*", "com.sun.*", "org.xml.*", "org.apache.*"})
// @PrepareForTest({HSSFWorkbook.class,HSSFCellStyle.class}) 不使用该注解
public class Demo {
@Test
public void test() throws Exception{
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet();
}
}
程序运行成功!
使用@PrepareForTest
@SpringBootTest
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PowerMockIgnore({"javax.*.*", "com.sun.*", "org.xml.*", "org.apache.*"})
@PrepareForTest({HSSFWorkbook.class})
public class Demo {
@Test
public void test() throws Exception{
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet();
}
}
我们在test()测试中完全没有用到powermock,但是为什么会失败呢?
原因:@PrepareForTest中的HSSFWorkbook.class,会告诉powermock提前准备这个类文件,那么当程序执行的时候,需要的该类的时候,就会使用到powermock准备的类
到目前为止,读者可能会认为 HSSFWorkbook wb = new HSSFWorkbook();
将会创建powermock准备的HSSFWorkbook对象,那么我debug程序,一探究竟
可以看到,这里new HSSFWorkbook()对象完全是一个正常的对象,而非powermock的对象,并且在该类中使用的也是这个真对象
直到运行到MockGateway这个类 才出现问题,在powermock中会有大量的代理类,拦截器,这些类中会使用到pokwermock的HSSFWorkbook的对象,而非真正的HSSFWorkbook对象,因此会出现问题
7.3 不是所有的类都可以Powermock
一个私有类是完全可以powermock的,那么是不是所有的类都可以powermock吗?
答案是:否定的()
@SpringBootTest
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PowerMockIgnore({"javax.*.*", "com.sun.*", "org.xml.*", "org.apache.*"})
@PrepareForTest({HSSFWorkbook.class})
public class Demo {
@Test
public void test3() throws Exception{
PowerMockito.mock(HSSFCellStyle.class);
}
}
定位至HSSFCellStyle 133行
我们发现这是一个编译期常量,跟ThreadLocal,是没办法跟powermock对象一起创建的
切记:powermock对象是无法改变编译期常量的
7.4 @InjectMock @Mock区别
@InjectMocks
private SomeHandler someHandler;
@Mock 或者 @Spy
private OneDependency oneDependency; // 此mock将被注入到someHandler
这里的@InjectMocks和@Autowired功能完完全全一样,唯一不同的是,@InjectMocks可以使oneDependency这个Mock对象自动注入到someHandler这个对象中。注意:①@InjectMocks所表示的对象及someHandler是一个普通的对象 ②Mock所表示的对象及oneDependency是一个Mock对象
7.5 @Mock和@MockBean的区别
@MockBean 会被装配到相关的类中 代替@Autowired
@Mock 不会被装配到相关的类中 无法代替@Autowired
7.6 Mock方法中的嵌套方法
Mockito.when(alarmRulesDao.changeAlarmLevel(Mockito.anyInt(),Mockito.anyInt()))
.thenReturn(-1);
Integer changeNumber = alarmRulesDao.changeAlarmLevel(changeAlarmlevelRequest.getId(), changeAlarmlevelRequest.getAlarmLevel());
即使Mock了changeAlarmLevel方法,其中的
changeAlarmlevelRequest.getId()
changeAlarmlevelRequest.getAlarmLevel()
还是会正常执行的
7.7 mock对象中的参数不要再做运算
this.getHSSFWorkbook(downloadVO.getSheetName(), downloadList));
mock的时候不能
Mockito.anyString(),Mockito.anyList()
而要
Mockito.any(),Mockito.anyList() 因为mock对象中的参数执行了相关运算
参考资料:
https://zhidao.baidu.com/question/390585793246337165.html
https://www.cnblogs.com/ysocean/p/6889906.html
http://www.managershare.com/post/355904
https://www.cnblogs.com/hunterCecil/p/5721468.html
https://qicen.iteye.com/blog/1928257
出处: https://www.cnblogs.com/AdaiCoffee/
本文以学习、研究和分享为主,欢迎转载。如果文中有不妥或者错误的地方还望指出,以免误人子弟。如果你有更好的想法和意见,可以留言讨论,谢谢!