教育单元测试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框架)简单介绍
我们在做单元测试的时候,常常困扰于数据的持久化问题,很多情况下我们不希望单元测试影响到数据库中的内容,而且受数据库的影响有时我们的单元测试的速度会很慢,所以我们往往希望将持久化部分隔离开,做单元测试的 ...
随机推荐
- Programming internal SRAM over SWD
https://github.com/MarkDing/swd_programing_sram // // Copyright (c) 2013 SILICON LABORATORIES, INC. ...
- GEF最简单的入门-helloword(1)
最近做插件项目.主要负责GEF这块. 好吧.资料真少的可以.特别是入门.都是一大堆一大堆的.网上最火的八进制的文章但对于我这种菜鸟级别看了还是一头雾水.各种资料折腾了半天.终于折腾出一个真正的入门例子 ...
- codeforces round #257 div2 C、D
本来应该认真做这场的.思路都是正确的. C题,是先该横切完或竖切完,无法满足刀数要求.再考虑横切+竖切(竖切+横切), 由于横切+竖切(或竖切+横切)会对分割的东西产生交叉份数.从而最小的部分不会尽可 ...
- AngularJS中Directive间交互实现合成
假设需要烹饪一道菜肴,有3种原料,可以同时使用所有的3种原料,可以使用其中2种,也可以使用其中1种. 如果以Directive的写法,大致是:<bread material1 material2 ...
- X.509 数字证书结构和实例
http://www.cppblog.com/sleepwom/archive/2010/07/08/119746.html 一. X.509数字证书的编码 X.509证书的结构是用ASN1(Abst ...
- windows phone 基础
一.安装 建议安装Windows 7环境,XP中不能运行模拟器,Vista系统支持,但不解释.系统安装完后,直接去微软网站在线安装即可,非常方便,美中不足的是如果你的网速不快,那可能要折磨你半天,快得 ...
- springboot中配置druid允许一次执行多条sql
原文:https://blog.csdn.net/jiangjun0130/article/details/77868578 1:在配置文件中不需要指定wall防火墙filter. 配置如下: spr ...
- java容器HashMap原理
1.为什么需要HashMap 前面我们说了ArrayList和LinkedList,它们对容器内的对象都能实现增.删.改.查.遍历等操作, 并且对应不同的情况,我们可以选择不同的List,用以提高效率 ...
- 找不到"javax.servlet.annotation.WebServlet"解决方法
以前创建的一个项目,打开的时候总是报错. import javax.servlet.annotation.WebServlet; 后来想起当时这个项目是发布在tomcat7.0下面的, 也就是说当时这 ...
- 超感猎杀/超感八人组第一季至二季/全集Sense8迅雷下载
本季 Sense8 (2015)看点:<超感八人组>由沃卓斯基姐弟执导的科幻剧集是Netflix继“纸牌屋第二季”后的又一大手笔制作,讲述未来世界不同地区的8个人因同时目睹同一暴力事件.从 ...