教育单元测试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框架)简单介绍
我们在做单元测试的时候,常常困扰于数据的持久化问题,很多情况下我们不希望单元测试影响到数据库中的内容,而且受数据库的影响有时我们的单元测试的速度会很慢,所以我们往往希望将持久化部分隔离开,做单元测试的 ...
随机推荐
- IAR EWAR 内联汇编 Error[Og010], Error [Og005], Error [Og006]
Error [Og005] + [Og006] when using inline assembler EW targets: 430, ARM, AVR EW component: C/C++ co ...
- [置顶] Android系统移植与调试之------->build.prop文件详细赏析
小知识:什么是build.prop? /system/build.prop 是一个属性文件,在Android系统中.prop文件很重要,记录了系统的设置和改变,类似於/etc中的文件.这个文件是如 ...
- 使用 NuGet 管理我们的程序集 - 预发行版
1.缘起 在我们的项目中.须要引用的组件统一放在一个 Libs 文件夹下.不管对于平台上的公共组件.还是应用模块,都是如此. 假设一个应用模块,比如能源管理(EM).要引用平台提供的公共组件,比如数据 ...
- cmd 递归删除目录或文件
递归删目录 for /r <TARGET DIR> %i in (<DIR NAME or Pattern>) do rd /s /q %i 递归删文件 for /r < ...
- Windows Phone本地数据库(SQLCE):14、删除数据(翻译)
这是“windows phone mango本地数据库(sqlce)”系列短片文章的最后一篇第十四篇. 为了让你开始在Windows Phone Mango中使用数据库,这一系列短片文章将覆盖所有你需 ...
- 线程池框架executor
Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...
- OpenLdap+MySQL笔记
20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送) 国内私募机构九鼎控股打造,九鼎投资是在全国股 ...
- EM算法与混合高斯模型
非常早就想看看EM算法,这个算法在HMM(隐马尔科夫模型)得到非常好的应用.这个算法公式太多就手写了这部分主体部分. 好的參考博客:最大似然预计到EM,讲了详细样例通熟易懂. JerryLead博客非 ...
- Hadoop-2.2.0中文文档—— Common - Native Libraries Guide
概览 这个新手教程描写叙述了native(本地?原生?)hadoop库,包括了一小部分关于native hadoop共享库的讨论. This guide describes the native ha ...
- 深入浅出!从语义角度分析隐藏在Unity协程背后的原理
Unity的协程使用起来比较方便,但是由于其封装和隐藏了太多细节,使其看起来比较神秘.比如协程是否是真正的异步执行?协程与线程到底是什么关系?本文将从语义角度来分析隐藏在协程背后的原理,并使用C++来 ...