Mockito&PowerMockito的原理与使用
单元测试简介
写单元测试应该是每个初级程序员都很厌烦还不能跳过的一个大坑,理想的状态下,项目的单元测试应该至少覆盖主要逻辑,对于一个方法来说,单元测试应该覆盖到各种边界条件,在每次对代码进行修改后,单元测试会帮助你检查修改是否对原始逻辑产生了影响。
Java web项目单元测试
目前在Java web领域,主要的单元测试框架有下面几种:
JUnit
Junit大名鼎鼎,目前最新版本为Junit5,https://junit.org/junit5/,之前使用比较广泛的是Junit4,使用起来十分简单,只需添加如下的maven依赖:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
然后在对应的测试方法上增加**@Test注解,除此之外,还提供@Before,@After**等注解,这个框架专注于单元测试,对于测试的数据来源没有做过多的优化,我们知道,在spring项目中,大部分bean都是通过配置/注解的方式注入进来的,用Junit做测试时,对于每个测试类都需要初始化一次spring容器,将相应的bean注入进去,这个开销十分巨大。
Spring-Test
顾名思义,是Spring Framework大家族的一部分,与其他框架不同,spring-test将IOC整合到了单元测试中,在使用spring-Test时候,首先还是要加入maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
然后我们可以通过创建基类的方式,在基类中对配置文件进行加载,并初始化容器,可以更方便的通过JUnit来进行测试
Mockito
这一部分是文本的重点,在对方法进行测试时,如何科学的处理依赖也是一个大问题,在单元测试时,我们仅关心当前方法中的逻辑,为了减少开销,对于依赖的外部bean,可以采用mock的方式来处理。在使用时,首先是加入mockito的依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.7.0</version>
</dependency>
在对对象进行mock时,有如下的方式:
简单对象Mock

对于复杂对象Mock(对象内部存在对其他bean的依赖)
如下图所示,testService依赖dependencyService,可以通过@InjectMocks注解来标记被注入的对象,用@Mock来表示注入的对象,然后在@Init方法中进行初始化,这样就可以将dependencyService注入到testService对象中。

在对方法进行mock时通过下图的方式,直接对方法的返回值内容进行mock(注意,这种mock方式本质上没有调用方法,所以即使getName方法中对dependencyService对象的某个属性进行了操作,也不会被执行到,如果你确实需要改变dependencyService中的属性,需要用spy的方式来生成对象,不能用mock()),除了通过thenReturn指定返回值,还可以通过thenThrow指定异常,或者通过verify来验证调用的次数、顺序等。

PowerMockito
PowerMockito其实与Mockito十分相似,只是比Mockito的功能更为全面,可以mock private方法、静态方法等
mock静态方法
首先需要在测试类上增加注解@RunWith(PowerMockRunner),@PrepareForTest({xxx.class});
其次要在init的时候通过PowerMockIto.mockStatic方法mock class
最后在调用的时候,与mockito类似。

单元测试的编写提示
- 常见的web项目中,会将代码分为Web层、数据层、业务层等,因为每一层的测试要点不同,可以采取不同的框架,比如:controller层采用spring mockmvc、数据层采用datajpatest、业务层采用Mockito.
- 测试的结果一般采用断言来判断,assert
Mock原理
mockito的原理
首先观察一下,when(dependencyService.getName(any(String.class))).thenReturn(“cc”)
发现when方法其实是调用了mockito_core对象的方法,如下图所示:


继续向下
整体的逻辑是先做一些mock操作获得MockingProgress 对象,然后通过stubbingStarted方法标记开始,再用mockingProgress pullOngoingStubbing()获得一个OngoingStubbing对象返回,看起来很奇怪,传入的参数methodcall并没有被调用?

看到mockingProgress方法是从threadlocal中取出当前上下文中的内容,也就是说methodcall是通过上下文的方式来进行记录的

查看stubbingStarted方法做了啥 1.验证状态(主要是验证stubbingInProgress是不是为空);2.生成了一个对象

查看pullOngoingStubbing做了啥? 就是返回了目前的ongoingStubbing,并将它设置为空(用完就清除)

那么ongoingStubbing对象是何时进行赋值的呢?如下图所示,是一个handler,ongoingStubbing对象是通过invocationContainer对象生成的

而invocationContainer对象又是从createMock的时候进行创建,这个mock方法就是我们在mockito的简单对象mock中所调用的mock方法

在这个方法中,需要着重看的就是createMock这个方法,这个方法有不同的子类实现,分别表示不同的代理方式,有cglib,也有byteBuddy,由此可知,mock的本质也是通过动态代理实现。


PowerMockito原理
powerMockito.when()方法其实就是直接调用了Mockito

直接看一下create的时候创建的对象,有点奇怪,有synchronized,并且返回值为void,mock对象去哪了?

继续查看MockCreator.mock方法,有返回值,但是没有使用,createMethodInvocationControl生成一个mockData,然后将类型和mockData中的内容放入MockRepository。

CreateMethodInvocationControl方法:

总结
当我们调用 mock 对象的 xx方法时,Mockito 会拦截方法的调用然后将方法调用的详细信息保存到 mock 对象的上下文中,当调用到 Mockito.when 方法时,实际上是从该上下文中获取最后一个注册的方法调用,然后把 thenReturn 的参数作为其返回值保存,然后当我们再次调用 mock 对象的该方法时,之前已经记录的方法行为将被再次回放,该方法触发拦截器重新调用并且返回我们在 thenReturn 方法指定的返回值,也就是说,你甚至可以写when(“ssss”).thenReturn()这种类型的代码。
实现Mockito框架的建议版本也并不复杂,因为其实本质上mock只需要在proxy中存储一个map,key为方法的invocation(也就是函数名+参数),value为你期待的调用结果,在when的时候初始化好这个map,使用的时候在代理中直接get取出对应的value,不要进行真实调用,就好了,Mokito使用cglib的方式,所以不支持静态类、静态方法的mock ,看源码+查资料发现 Mockito 其实并没有使用我们熟悉的 Spring AOP 或者 AspectJ 做的方法拦截,而是通过运行时增强库 Byte Buddy 和反射工具库 Objenesis 生成和初始化 mock 对象的。
可以看出,整个信息的传递过程是在上下文中,也就是说Mockito框架提供的函数是有副作用的,是违反了设计模式的~
参考文献
http://ifeve.com/%E4%B8%80%E6%96%87%E8%AE%A9%E4%BD%A0%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B-mockito-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%A1%86%E6%9E%B6/

本文介绍了单元测试的重要性和Java Web项目的单元测试框架,如JUnit和Spring-Test。重点讲解了Mockito和PowerMockito的使用,包括mock简单对象和复杂对象、mock静态方法。此外,还探讨了Mockito的原理,通过动态代理实现方法拦截,并简述了PowerMockito的原理。最后,提供了单元测试编写的一些建议。

1575

被折叠的 条评论
为什么被折叠?



