首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >EasyMock技术解密

EasyMock技术解密

作者头像
京东技术
发布于 2023-09-21 09:16:52
发布于 2023-09-21 09:16:52
30300
代码可运行
举报
文章被收录于专栏:京东技术京东技术
运行总次数:0
代码可运行

Tech

导读 本文主要介绍了EasyMock平台及JSF Mock实现技术,后续会继续编写一系列文章,分享更多Mock相关技术。EasyMock平台在2021年开展开源共建,并获得2021年行云1024研发效能共建最佳新锐奖,目前正在集团开源共建中,欢迎联系我们一起共建!

01

EasyMock平台介绍

在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!

EasyMock平台面向集团产品、研发、测试人员,提供的一款完全模拟服务端Mock的平台,支持JSF、HTTP接口Mock服务,支持测试环境/线上环境多站点,灵活的接口出入参设置,可以方便返回想要的Mock数据。平台自2021年10月上线行云,面向集团进行推广,累计接入C1部门 49个(涉及零售、科技、物流、健康等BU),涵盖用户2000+,月均Mock调用量1000万+

首先了解下EasyMock解决的问题:

1.解决依赖服务不可用问题,不阻碍开发/测试;

2.依赖服务复杂、异常数据无法支持,弥补场景缺失;

3.依赖服务数据经常变化,通过Mock提升自动化测试通过率;

4.项目测试时间紧张时,可不受依赖服务的排期影响。

其次可以通过一个视频,了解平台JSF Mock的使用过程:

以上只是Mock平台的部分功能,平台还有更多内容值得探索。

接下来了解EasyMock提供的平台能力

1.支持多协议Mock:JSF、HTTP;

2.支持测试/线上环境;

3.同接口多版本、多别名支持;

4.接口与方法分开控制,支持服务透传,调用真实服务;(平台亮点)

1)方法级别透传:被测应用调用同一接口的不同方法,可实现一个方法Mock,一个方法调用真实的服务;

2)参数模版级别:被Mock的方法匹配不到参数模版时,可设置调用真实的服务(即将上线);

5. 参数数据模板管理:支持参数正则匹配、出入参自动解析、自动生成、参数化、参数传递、异常模拟等;(平台亮点)

1)支持参数正则匹配:多种参数匹配方式,优先全量匹配、部分匹配、正则匹配、默认匹配;

2)出入参自动解析、自动生成:不知道出入参格式怎么办?平台支持参数解析、出参自动生成;

3)参数传递:想返回的出参取客户端调用传进来的入参值;

4)异常模拟:支持模拟接口抛出的异常、超时(即将上线);

5)参数化:支持出参参数化、简单运算;

6. 开放API服务,方便自动化或其他平台集成;

7. 性能测试支持;

8. 更多功能持续迭代中。

02

平台实现技术解密-JSF Mock

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。从设计稿出发,提升页面搭建效率,亟需解决的核心问题有数据来源:交易数据的来源,包含业务信息、联系人、数据接入协议

Mock所用的技术知识点很多,比如JVM、类实例化、动态代理、反序列化、Http拦截等,本文将对Mock所用技术进行全面解密,主要分享平台的整体设计及JSF Mock的实现技术,后期也会针对某一块的技术实现或实践案例进行详细的分享。

2.1 平台整体设计

如下图所示,平台整体采用主、从服务部署,主服务面向用户,提供服务管理、模版管理、应用管理(规划中)、看板等功能,从服务提供接口Mock服务,供客户端调用,主服务通过IP分配规则控制从服务进行接口Mock开启/关闭。

图1 平台全景图

2.2 JSF技术实现步骤

从技术角度来说下JSF Mock的整个流程,用户访问平台,添加要Mock的JSF接口和方法,主服务会异步下载接口所依赖的Jar包,用户开启Mock,主服务按分配规则通知从服务开启Mock,从服务将接口所依赖Java类加载到JVM,通过动态代理将接口实例化,同时将接口注册到JSF册中心,一个接口就Mock完成。这时客户端请求Mock服务,从服务接收到客户端请求,后台根据接口、方法匹配Mock接口,同时根据客户端请求的入参进行参数匹配,匹配到设置的参数,通过反序列化将出参返回。可以将整体流程概况为7个技术知识点,然后逐一讲解:

图2 JSF技术实现步骤

数据来源:交易数据的来源,包含业务信息、联系人、数据接入协议等。

订单模版:交易履约订单中心采用泛化的格式存储交易数据,针对每个交易场景配置一个订单模版,模版上配置映射规则来解析数据。

跟单:履约订单中心接收满足某些条件的交易数据。

补单:当数据源的数据不完整或不满足业务场景需求,履约订单中心请求外部接口来补充交易数据。

推送模版:履约订单中心将交易数据推送到下游系统。

2.2.1 Jar包下载

用户在添加JSF接口时,需要指定pom坐标,后台程序根据pom坐标去下载所需要的Jar包,并存储在NFS服务器。实现流程如下:

1.指定pom文件,未指定则去maven私服获取最新上传的jar包;这里支持排除exclusions2.根据pom坐标,生成pom文件3.异步下载(@EnableAsync),执行mvn命令:mvn clean dependency:copy-dependencies,这地方会将该接口所依赖的Jar包都会进行下载;

新增接口页面:

图3 新增接口页面

2.2.2 JVM加载

下载Jar包后,需要通过ClassLoader将Jar包加载到JVM,这里采用URLClassLoader进行加载,URLClassLoader继承于ClassLoader,支持从Jar文件和文件夹中获取Class。首先获取系统的classLoader,遍历Jar包进行动态加载,最后通过loadClass加载接口类。

示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 // 拿到系统的classLoader
        URLClassLoader urlClassLoaderForJvm = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Class<URLClassLoader> urlClass = URLClassLoader.class;
        Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
        method.setAccessible(true);
        for (
                File file : files) {
            logger.info("动态加载jar包:{}", file.getAbsolutePath());
            URL url = new URL("file:" + file);
            method.invoke(urlClassLoaderForJvm, new Object[]{ url });
        }
        try {
            cls = urlClassLoaderForJvm.loadClass(interfaceName);
        } catch (
                NoClassDefFoundError e) {
            logger.error("不能正常解析类NoClassDefFoundError, name:" + interfaceName);
        } catch (Exception e) {
            logger.error(" 不能正常解析类Exception: " + e.toString());
        }

ClassLoader结构

图4 ClassLoader结构

2.2.3 类实例化

类实例化主要通过动态代理实现,Java动态代理位于java.lang.reflect包下,一般主要涉及到以下两个类:

1. InvocationHandler:该接口中仅定义了一个方法,每一个代理都要实现接口InvocationHandler,通过invoke进行调用方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Object invoke(Object proxy, Method method, Object[] args)throwsThrowable

2. Proxy:该类即为动态代理类,这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是newProxyInstance 这个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handdler)throwsIllegalArgumentException

loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载;

interfaces:一个Interface对象的数组,表示的是将要需要代理的对象提供一组什么接口,如果提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样能调用这组接口中的方法了;

handler:一个InvocationHandler对象,表示的是当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上返回代理类的一个实例。

图5 动态代理示例

动态代理实现步骤:

1.创建一个实现接口InvocationHandler的类,并实现invoke方法;

2.创建被代理的类以及接口;

3.调用Proxy的静态方法,创建一个代理类Proxy.newProxyInstance(classLoader, interfaces, proxy);

4.通过代理调用方法。

代码示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * JDK动态代理代理类
 *
 */
@Service
public class FacadeProxy implements InvocationHandler {

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
        // mock处理

    // mock参数匹配
    String response = matchParams(int methodId,Object[] args,Method method);
    // mock出参返回
    return new Gson().fromJson(response, method.getReturnType());

  }

  public static <T> T newMapperProxy(Class<T> mapperInterface) throws Throwable{
    ClassLoader classLoader = mapperInterface.getClassLoader();
    Class<?>[] interfaces = new Class[] { mapperInterface };      
    FacadeProxy proxy = new FacadeProxy();      
    T t = (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
    return t;
  }
}

2.2.4 JSF接口注册/注销

采用JSF API的方式进行接口注册/注销。目前的API方式和Spring方式里的属性都是一一对应的,spring的方式无非就是spring转换为api的方式进行发布。

这里参考JSF API即可:https://cf.jd.com/pages/viewpage.action?pageId=296129902

2.2.5 客户端调用

Mock接口注册到JSF注册中心,客户端调用mock别名(Alias)即可。

2.2.6 参数匹配

参数匹配这里会依顺序进行以下四种方式匹配,匹配到就直接返回。

1. 优先对象匹配:参数截取->参数转对象->对象比较;

2. 字符串完成匹配、部分匹配;

3. 正则匹配:Java正则匹配;

4. 默认匹配:.*或*。

2.2.7 参数返回

匹配到数据模版后,将匹配到的出参转换成客户端想要的类型则需要将出参进行反序列化,转换为mock接口对应的出参类型返回。

反序列化是本文的一个难点,出参类型格式各样,通过进行各种尝试,不敢说所有,至少当前接入的接口都已支持。

参数类型主要有以下几种:基本类型、字符串、简单对象、复杂对象、泛型;对于基本类型、字符串,转换为对应类型直接返回即可;对于简单对象,通过fastjson转换即可;对于泛型、复杂对象,会尝试fastjson、gson、指定class 3种方式进行转换。

代码示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 1.获取Mock接口出参类型:
Type genericReturnType = method.getGenericReturnType();
// 2.基本类型转换:
object = Integer.valueOf(response); 
// 3.优先fastjson转换返回:
object = JSON.parseObject(resultString,genericReturnType);
// 4.fastjson转换对象失败,改为gson转换
object = gson.fromJson(resultString,genericReturnType);
// 5. 返回对象为Object,客户端解析时需要具体的类,这时需要在返回参数指定class,这个通过PojoUtils提供的realize方法转换
object=JSON.parseObject(resultString,Map.class);

03

总结

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。从设计稿出发,提升页面搭建效率,亟需解决的核心问题有:

以上为JSF Mock的实现过程,后续会继续分享HTTP Mock的实现过程及平台开发过程中解决的各种技术难点。目前EasyMock正在开源共建中,也欢迎更多有想法的小伙伴一起共建,进行技术交流,打造集团高质量Mock产品。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023/02/14 19:00:00,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 京东技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Tech
  • 导读 本文主要介绍了EasyMock平台及JSF Mock实现技术,后续会继续编写一系列文章,分享更多Mock相关技术。EasyMock平台在2021年开展开源共建,并获得2021年行云1024研发效能共建最佳新锐奖,目前正在集团开源共建中,欢迎联系我们一起共建!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档