开发者社区 > 博文 > EasyMock技术解密
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

EasyMock技术解密

  • yu****
  • 2022-08-04
  • IP归属:北京
  • 15040浏览

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

    一、EasyMock平台介绍

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

    平台地址: http://jagile.jd.com/mock/singleInterface

    帮助文档: http://doc.jd.com/rest/mock/ 

    咚咚支持群:1024782579

    首先,我们先了解下EasyMock解决的问题:

    1. 解决依赖服务不可用问题,不阻碍开发/测试; 
    2. 依赖服务复杂、异常数据无法支持,弥补场景缺失;
    3. 依赖服务数据经常变化,通过Mock提升自动化测试通过率;
    4. 项目测试时间紧张时,可不受依赖服务的排期影响;

    然后,我们通过一个小的GIF,了解平台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. 更多功能持续迭代中;

    二、平台实现技术解密-JSF Mock

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

    平台整体设计

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


    JSF技术实现步骤

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

    1、Jar包下载

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

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

    新增接口页面:

    2、JVM加载

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

    示例代码:

            // 拿到系统的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结构

    3、类实例化

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

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

    Object invoke(Object proxy, Method method, Object[] args)throwsThrowable

        proxy:指代理类  method:被代理的方法  args:被代理的方法参数

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

    Public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handdler)throwsIllegalArgumentException

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

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

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

    动态代理实现步骤:

    1. 创建一个实现接口InvocationHandler的类,并实现invoke方法
    2. 创建被代理的类以及接口
    3. 调用Proxy的静态方法,创建一个代理类Proxy.newProxyInstance(classLoader, interfaces, proxy)
    4. 通过代理调用方法

    代码示例:

    /**
     * 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;
    	}
    }

    4、JSF接口注册/注销

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

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

    5、客户端调用

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

    6、参数匹配

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

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

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

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

    3. 默认匹配: .*或*

    7、参数返回

    匹配到数据模版后,如何将匹配到的出参转换成客户端想要的类型呢,这里需要将出参进行反序列化,转换为mock接口对应的出参类型返回。反序列化是本文的一个难点,出参类型格式各样,我们进行了各种尝试,不敢说所有,至少当前接入的接口都已支持。参数类型主要有以下几种:基本类型、字符串、简单对象、复杂对象、泛型;对于基本类型、字符串,转换为对应类型直接返回即可;对于简单对象,通过fastjson转换即可;对于泛型、复杂对象,会尝试fastjson、gson、指定class 3种方式进行转换。在出参类型反序列化这里遇到很多坑,后面可以进行专题分享。

    代码示例:

    // 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);
    object = PojoUtils.realize(object,genericReturnType.getClass());
    


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

    联系方式:

    【平台地址】:  http://jagile.jd.com/mock/singleInterface 

    【咚咚群】:1024782579

    【联系人】:张达(bjzhangda) 郭玉锐(bjguoyurui) 陈睿(chenrui142)


    文章数
    1
    阅读量
    376

    作者其他文章