教育单元测试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框架)简单介绍
我们在做单元测试的时候,常常困扰于数据的持久化问题,很多情况下我们不希望单元测试影响到数据库中的内容,而且受数据库的影响有时我们的单元测试的速度会很慢,所以我们往往希望将持久化部分隔离开,做单元测试的 ...
随机推荐
- sqlserver 2012 查询时提示“目录名称无效”
重装系统或者用360等软件清理了相应的临时文件导致解决:在运行中输入 %temp% 回车,会跳出找不到路径的提示,然后到提示的目录建没有找到的目录文件夹即可.
- HDU4607(求树中的最长链)
题目:Park Visit 题意:给定一棵树,从树中的任意选一个顶点出发,遍历K个点的最短距离是多少?(每条边的长度为1) 解析:就是求树的最长链,假设求出的树的最长链所包含的点数为m,那么如果K&l ...
- User Agent Strings列表 — Kejun's Blog
曾经,ME 就需要这里的东西了. http://www.useragentstring.com/pages/All/
- 查看sqlserver2008数据库服务器实例名称
select @@SERVICENAME 安装SQLServer时,如果不另外设置数据库实例名称,那么默认的数据库实例名就是MSSQLSERVER
- 给第三方dll强签名
假若我们要对第三方控件或者是其他的没有源代码的DLL文件想做类似的处理,增加强名称签名,怎么处理,是很多人都会面对的问题. 步骤: 1.首先采用反汇编工具ildasm生成中间语言. ildas ...
- read(10, "NTP0 13690\n", 64) 数据库登录缓慢
初步怀疑:下面两个參数引起: SQL> conn / as sysdba Connected. SQL> show parameter lock NAME ---------------- ...
- iOS 32位、 64位系统兼容性设置-Xcode创建支持IOS4.3以上版本的应用的方法
方法一: 如果是Xcode 5的话步骤为 点击项目名称->Build Settings->搜索 Architectures 这个里面的原始的值是Standard architectures ...
- android多设备界面适配的利器:属性weight的妙用
1.按比例显示控件元素 <EditText android:id="@+id/edit_message" android:layout_weight="2" ...
- 神探夏洛克第一季/全集Sherlock1迅雷下载
第一季 Sherlock Season 1 (2010)看点:夏洛克·福尔摩斯(Sherlock Holmes)是一个虚构的侦探人物,是由19世纪末的英国侦探小说家阿瑟·柯南·道尔所塑造的一个才华横溢 ...
- 详细解读LruCache类
LruCache是android提供的一个缓存工具类,其算法是最近最少使用算法.它把最近使用的对象用“强引用”存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前就从内 ...