天天看點

EasyMock技術解密

作者:閃念基因

大家好,本篇分兩部分,主要介紹EasyMock平台及JSF Mock實作技術,後續會繼續編寫一系列文章,分享更多Mock相關技術。平台21年零售内開展開源共建,并獲得21年行雲1024研發效能共建最佳新銳獎,目前正在集團開源共建中,歡迎聯系我們一起共建!

一、EasyMock平台介紹

EasyMock平台面向集團産品、研發、測試人員,提供的一款完全模拟服務端Mock的平台,支援JSF、HTTP接口Mock服務,支援測試環境/線上環境多站點,靈活的接口出入參設定,可以友善傳回想要的Mock資料。平台自21年10月上線行雲,面向集團進行推廣,累計接入C1部門 49個(涉及零售、科技、物流、健康等BU),涵蓋使用者2000+,月均Mock調用量1000萬+。

首先,我們先了解下EasyMock解決的問題:

1.解決依賴服務不可用問題,不阻礙開發/測試;

2.依賴服務複雜、異常資料無法支援,彌補場景缺失;

3.依賴服務資料經常變化,通過Mock提升自動化測試通過率;

4.項目測試時間緊張時,可不受依賴服務的排期影響;

然後,我們通過一個小的GIF,了解平台JSF Mock的使用過程

,時長01:22

以上隻是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開啟/關閉。

EasyMock技術解密

JSF技術實作步驟

從技術角度來說下JSF Mock的整個流程,使用者通路平台,添加要Mock的JSF接口和方法,主服務會異步下載下傳接口所依賴的Jar包,使用者開啟Mock,主服務按配置設定規則通知從服務開啟Mock,從服務将接口所依賴Java類加載到JVM,通過動态代理将接口執行個體化,同時将接口注冊到JSF冊中心,一個接口就Mock好了。這時用戶端請求Mock服務,從服務接收到用戶端請求,背景根據接口、方法比對Mock的接口,同時根據用戶端請求的入參進行參數比對,比對到設定的參數,通過反序列化将出參傳回。可以将整體流程概況為7個技術知識點,然後逐一講解:

EasyMock技術解密

1

Jar包下載下傳

使用者在添加JSF接口時,需要指定pom坐标,背景程式根據pom坐标去下載下傳所需要的Jar包,并存儲在NFS伺服器。實作流程如下:

1.指定pom檔案,未指定則去maven私服擷取最新上傳的jar包;這裡支援排除exclusions2.根據pom坐标,生成pom檔案3.異步下載下傳(@EnableAsync),執行mvn指令:mvn clean dependency:copy-dependencies,這地方會将該接口所依賴的Jar包都會進行下載下傳;

新增接口頁面:

EasyMock技術解密

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結構

EasyMock技術解密

3

類執行個體化

類執行個體化主要通過動态代理實作,Java動态代理位于java.lang.reflect包下,一般主要涉及到以下兩個類:

1. InvocationHandler:該接口中僅定義了一個方法,每一個代理都要實作接口InvocationHandler,通過invoke進行調用方法。

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

2. Proxy:該類即為動态代理類,這個類的作用就是用來動态建立一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是newProxyInstance 這個方法:

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

loader:一個ClassLoader對象,定義了由哪個ClassLoader對象來對生成的代理對象進行加載;

interfaces:一個Interface對象的數組,表示的是将要需要代理的對象提供一組什麼接口,如果提供了一組接口給它,那麼這個代理對象就宣稱實作了該接口(多态),這樣能調用這組接口中的方法了;

handler:一個InvocationHandler對象,表示的是當這個動态代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上傳回代理類的一個執行個體;

EasyMock技術解密

動态代理實作步驟:

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正則比對

4. 預設比對:.*或*

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産品。

作者:Y-履約 郭玉銳

來源:微信公衆号:京東零售技術

出處:https://mp.weixin.qq.com/s/UD0TkqTaXtAJcJqR4RB_Dg