Mockito&PowerMockito的原理与使用

本文介绍了单元测试的重要性和Java Web项目的单元测试框架,如JUnit和Spring-Test。重点讲解了Mockito和PowerMockito的使用,包括mock简单对象和复杂对象、mock静态方法。此外,还探讨了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类似。
在这里插入图片描述

单元测试的编写提示

  1. 常见的web项目中,会将代码分为Web层、数据层、业务层等,因为每一层的测试要点不同,可以采取不同的框架,比如:controller层采用spring mockmvc、数据层采用datajpatest、业务层采用Mockito.
  2. 测试的结果一般采用断言来判断,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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值