大家好,本篇分兩部分,主要介紹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開啟/關閉。
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包;這裡支援排除exclusions2.根據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
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正則比對
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