教育单元测试mock框架优化之路(上)


public class MockitoTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (hasMockitoAnnotations(testContext)) {
//看这里
MockitoAnnotations.initMocks(testContext.getTestInstance());
}
injectFields(testContext);
}
可以看到Springboot的MockitoTestExecutionListener,在test实例化阶段,通过Mockito框架的MockitoAnnotations来对test实例进行mock注解扫描和注入。再具体跟进下MockitoAnnotations.initMocks实现内部,可以发现问题出现在其MockInjectionStrategy的实现上。我拿其中一种MockInjectionStrategy策略(Spy注入策略)来举例说明。
public class SpyOnInjectedFieldsHandler extends MockInjectionStrategy {
@Override
protected boolean processInjection(Field field, Object fieldOwner, Set
public class MockListener extends AbstractTestExecutionListener {
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
testContext.setAttribute(TEST_CONTEXT_MOCKS_MAP_KEY, new HashMap());
testContext.setAttribute(TEST_CONTEXT_SPYS_MAP_KEY, new HashMap());
/*1. 为带@EduMock注解的属性创建Mock对象*/
createMocks(testContext);
/*2. 为带@Spy注解的属性创建Spy对象*/
createSpys(testContext);
/*3. 为带@EduInjectMocks注解的bean注入mock/spy对象*/
injectIntoTargetBeans(testContext);
}
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
/*还原为真实的对象*/
Map> injectedBeanMap = getInjectedBeanMap(testContext);
/*恢复bean依赖的真实对象*/
for (Map.Entry> entry : injectedBeanMap.entrySet()){
Object targetBean = entry.getKey();
Map originalFieldMap = entry.getValue();
unInjectTargetBean(targetBean, originalFieldMap);
}
testContext.removeAttribute(TEST_CONTEXT_MOCKS_MAP_KEY);
testContext.removeAttribute(TEST_CONTEXT_SPYS_MAP_KEY);
testContext.removeAttribute(TEST_CONTEXT_INJECTED_MAP_KEY);
}
这是其中injectIntoTargetBean部分的逻辑:
private void injectIntoTargetBean(TestContext testContext, Field targetBeanField) throws Exception{
Object testInst = testContext.getTestInstance();
Object injectedBean = targetBeanField.get(testInst); //被注入的目标Bean
//看这里
Object beanUnwrap = UnWrapProxyUtil.unwrapProxy(injectedBean); //被spring代理的真实bean
Class clazz = beanUnwrap.getClass();
final Field[] fields = clazz.getDeclaredFields();
Map mockMap = (Map)testContext.getAttribute(TEST_CONTEXT_MOCKS_MAP_KEY);
Map spyMap = (Map)testContext.getAttribute(TEST_CONTEXT_SPYS_MAP_KEY);
Map originalFieldMap = new HashMap();
for (Field field : fields){
Object mock = mockMap.get(field.getName());
Object spy = spyMap.get(field.getName());
if (null != mock || null != spy){
field.setAccessible(true);
/*暂存目标bean原有的属性,测试类执行完后需要替换回原有属性*/
originalFieldMap.put(field, field.get(beanUnwrap));
/*注入mock对象*/
if (null != mock){
ReflectionTestUtils.setField(beanUnwrap, field.getName(), mock);
}
/*注入spy对象*/
if (null != spy){
ReflectionTestUtils.setField(beanUnwrap, field.getName(), spy);
}
}
}
Map> injectedBeanMap = getInjectedBeanMap(testContext);
injectedBeanMap.put(beanUnwrap, originalFieldMap);
}
private void injectIntoTargetBean(TestContext testContext, Field targetBeanField) throws Exception{
Object testInst = testContext.getTestInstance();
Object injectedBean = targetBeanField.get(testInst); //被注入的目标Bean
//看这里
Object beanUnwrap = UnWrapProxyUtil.unwrapProxy(injectedBean); //被spring代理的真实bean
Class clazz = beanUnwrap.getClass();
final Field[] fields = clazz.getDeclaredFields();
Map mockMap = (Map)testContext.getAttribute(TEST_CONTEXT_MOCKS_MAP_KEY);
Map spyMap = (Map)testContext.getAttribute(TEST_CONTEXT_SPYS_MAP_KEY);
Map originalFieldMap = new HashMap();
for (Field field : fields){
Object mock = mockMap.get(field.getName());
Object spy = spyMap.get(field.getName());
if (null != mock || null != spy){
field.setAccessible(true);
/*暂存目标bean原有的属性,测试类执行完后需要替换回原有属性*/
originalFieldMap.put(field, field.get(beanUnwrap));
/*注入mock对象*/
if (null != mock){
ReflectionTestUtils.setField(beanUnwrap, field.getName(), mock);
}
/*注入spy对象*/
if (null != spy){
ReflectionTestUtils.setField(beanUnwrap, field.getName(), spy);
}
}
}
Map> injectedBeanMap = getInjectedBeanMap(testContext);
injectedBeanMap.put(beanUnwrap, originalFieldMap);
}
可以看出来,我们自定义的MockListener与springboot test框架的MockitoTestExecutionListener的区别在于:
1. 需要手动配置大量的dubbo consumer、amqp template等mock bean的xml配置。
图5. 大量的dubbo consumer的mock bean配置
图6. 大量的amqp的mock bean配置
2. 需要在产品端再额外单独定义(主要是因为namespace等属性不同)的Redis client,Memcache client,ZookeeperLockContext等bean的mock配置。
图7. 由于namespace不同而需要定义多个JedisClient的Mock Bean
图8. 由于beanName不同而需要定义多个ZookeeperLock的Mock Bean
3. 由于需要将单元测试的bean的mock配置部分单独出来,导致单元测试基类的import管理混乱和不稳定。甚至导致业务bean定义的xml组织需要考虑单元测试mock的需求。这样,不仅导致配置复杂,业务设计受单元测试影响,又或导致业务和单元测试发生较大不一致。
图9. 一个复杂的单元测试汇总xml配置文件
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoMock {
Class[] byClass() default {};//根据类型进行匹配
Class[] bySuperClass() default {};//根据基类进行匹配
MockSetting[] byMockSetting() default {};//根据复杂的匹配或过滤配置
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MockSetting{
Class byClass() ;
String propNameIncludeRegFilter() default ""; //符合正则匹配的属性会被传递
String propNameExcludeRegFilter() default "";//符合正则匹配的属性会被过滤
boolean copyContructArgs() default false;//传递构造函数参数
}
@Component
@AutoMock(byMockSetting = @MockSetting(byClass = ReferenceBean.class,propNameIncludeRegFilter = "^interface$"))
public class DubboReferenceMockFactoryBean extends ReferenceConfig implements FactoryBean<Object>{
@Override
public Object getObject() throws Exception {
return PowerMockito.mock(this.getInterfaceClass());
}
@Override
public Class<?> getObjectType() {
return this.getInterfaceClass();
}
@Override
public boolean isSingleton() {
return true;
}
public String getVersion() {
return null;
}
public void setVersion(String version) {
}
}
教育单元测试mock框架优化之路(上)的更多相关文章
- 教育单元测试mock框架优化之路(下)
转载:https://sq.163yun.com/blog/article/169563599967031296 四.循环依赖的解决 果然! 当我将@SpyBean应用到存在有循环依赖的Bean上时, ...
- 教育单元测试mock框架优化之路(中)
转载:https://sq.163yun.com/blog/article/169564470918451200 三.间接依赖的bean的mock替换 对于前面提供的@Mock,@Spy+@Injec ...
- 单元测试mock框架——jmockit实战
JMockit是google code上面的一个java单元测试mock项目,她很方便地让你对单元测试中的final类,静态方法,构造方法进行mock,功能强大.项目地址在:http://jmocki ...
- 单元测试Mock框架Powermockito 【mockito1.X】
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> &l ...
- 新浪微博iOS客户端架构与优化之路
新浪微博iOS客户端架构与优化之路 随着Facebook.Twitter.微博的崛起,向UGC.PGC.OGC,自媒体提供平台的内 容消费型App逐渐形成了独特的客户端架构模式.与电商和通讯工具类 ...
- 阿里巴巴 web前端性能优化进阶路
Web前端性能优化WPO,相信大多数前端同学都不会陌生,在各自所负责的站点页面中,也都会或多或少的有过一定的技术实践.可以说,这个领域并不缺乏成熟技术理论和技术牛人:例如Yahoo的web站点性能优化 ...
- 单元测试及框架简介 --junit、jmock、mockito、powermock的简单使用
转 单元测试及框架简介 --junit.jmock.mockito.powermock的简单使用 2013年08月28日 14:33:06 luvinahlc 阅读数:6413 标签: 测试工具单元测 ...
- 微博MySQL优化之路--dockone微信群分享
微博MySQL优化之路 数据库是所有架构中不可缺少的一环,一旦数据库出现性能问题,那对整个系统都回来带灾难性的后果.并且数据库一旦出现问题,由于数据库天生有状态(分主从)带数据(一般还不小),所以出问 ...
- MoQ(基于.net3.5,c#3.0的mock框架)简单介绍
我们在做单元测试的时候,常常困扰于数据的持久化问题,很多情况下我们不希望单元测试影响到数据库中的内容,而且受数据库的影响有时我们的单元测试的速度会很慢,所以我们往往希望将持久化部分隔离开,做单元测试的 ...
随机推荐
- bitnami下mysql配置-包含phpMyAdmin配置
mysql开启远程访问: 默认情况下mysql的绑定ip是bind-address=127.0.0.1 找到my.cnf bitnami@linux:~$ sudo find / -name my.c ...
- java 输入一个字符串,打印出该字符串中字符的所有排列
import java.util.Scanner; public class Demo001 { public static void main(String[] args) { String str ...
- 《完全用Linux工作》作者:王垠
完全用 GNU/Linux 工作 理解 GNU/Linux 注:本文是清华“牛仔”王垠的“成名作”,在网上引起很大的争议.对他崇拜地五体投地者有,对他嗤之以鼻者也有,总之成了一年多以前Linux 爱好 ...
- Modbus读写模拟量寄存器具体解释
读可读写模拟量寄存器: 发送命令(主机向从机)格式: [设备地址] [命令号03] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的高 ...
- springboot—spring aop 实现系统操作日志记录存储到数据库
原文:https://www.jianshu.com/p/d0bbdf1974bd 采用方案: 使用spring 的 aop 技术切到自定义注解上,针对不同注解标志进行参数解析,记录日志 缺点是要针对 ...
- PostgreSQL学习手册(目录)
原文地址:http://www.cnblogs.com/stephen-liu74/archive/2012/06/08/2315679.html 事实上之前有很长一段时间都在纠结是否有必要好好学习它 ...
- IntentService 与ResultReceiver
from://http://lyzhanghai.iteye.com/blog/947504 在google的I/O大会中关于“Writing zippy Android apps”,有讲过用Inte ...
- ibatis.net:惯用法
使用<![CDATA[]]>保持SQL格式 IN 查询
- rtsp实现的开源代码
* live.com C/S C++ http://www.live555.com * darwin S C++ http://www.opensource.apple ...
- redis + Tomcat 8 的session共享解决
如果英文不错的看,建议直接看官网吧,官网写的挺清楚.下面的内容是转载的一篇文章,自己补充了一些,供大家参考,也欢迎大家一起讨论 官方截止到2015-10-12前是不支持Tomcat8的,详情见官网:h ...