一、 背景
福祿網絡作為一家數字權益商品及服務提供商,覆蓋了我們衣食住行的各種生活場景的權益內容,對接了如支付寶、京東、銀行APP各種渠道,如何能夠快速的響應渠道需求,提供穩定的接口服務,這就要求我們電商團隊能夠做到比渠道快一步的接口測試;
同時作為一家集團化的公司,內部的信息化系統對接了眾多銀行的相關支付業務,涉及到查余額、下流水、支付、對賬等日常資金業務,這要求信息化部門能夠確保資金支付相關場景能夠在上線前進行完整覆蓋,業務方新的業務接入或者需求場景變更比較頻繁,版本的快速迭代背景下如何保證眾多的場景能夠快速覆蓋,通過完全真實的業務操作成本是巨大的;
二、 引入MOCK
基於上述的業務系統測試痛點,質量管理團隊決定引入mock服務。我們首先想到的是以最低的成本來完成,市面上有許多的mockserver的開源軟件,但在調研了相關的開源產品之后,我們發現沒有一款比較貼合我們業務需求的產品;
比如我們的資金支付相關場景對接的銀行方,都是以xml報文的格式作為請求和返回;有些場景對返回模板的數據是動態要求的,比如某個支付狀態第一次請求是處理中,第N次請求變為成功;而有些銀行通信協議是socket等,通過調研后我們決定自己來開發一套mock服務。
三、框架選型
- 界面可視化操作
為了保持系統風格的統一,我們決定沿用QECS平台的前端框架(QECS詳細介紹移步至:https://www.cnblogs.com/fulu/p/15419208.html) - 高性能
為了滿足高性能的需求,模板數據存儲我們采用redis;開始我們准備沿用QECS的后端框架flask,在進行了一系列的性能測試后,我們發現無法支撐我們5KTPS的需求,后來我們改用了springBoot,經過測試能夠達到我們的需求;
四、具體實現
4.1 設計方案
外部請求打入Mock服務,監聽服務獲取到請求通過Redis中的已有模板進行規則匹配,滿足匹配規則返回對應模板數據;不滿足返回無法匹配的數據提示。
4.2 功能說明
數據格式
目前匹配規則支持json、form、query、xml這幾種參數類型;返回支持json、xml和text這幾種數據類型
狀態碼
可以模擬返回200、500、304、502、503、400等各種http狀態碼
返回時間
可以設置請求mock服務后的返回間隔時間,對超時返回場景比較有效
動態取值
設置了內置函數和從匹配規則中取值這兩種方式,可以動態設置返回數據的字段值
自定義代碼
對於平台界面暫時無法支撐的個性化需求,可以通過在Hermes中通過代碼來實現,靈活方便
高性能
模板數據通過Redis進行存儲,請求進來到模擬返回通過redis進行匹配,能夠提供高性能的返回,可作為性能壓測的擋板服務
4.3 平台功能介紹
4.3.1 主界面
4.3.2 創建API,即我們需要模擬調用的對方接口
4.3.3 配置模板,配置API的期望模板
模板數據會存儲到REDIS
4.3.4 模擬請求
獲取實際請求值,獲取期望模板值,將模板與請求值進行比較匹配,當模板請求參數屬於真實請求參數的子集,匹配成功
4.3.5 自定義函數的實現
為了滿足個性化的使用場景,服務內置了一批內置函數來滿足動態取值的場景
private static String getReplaceStr(String str){
if(str.contains("Random")){
String[] strA = str.trim().split(",");
int i1 = strA[0].indexOf("(");
int i2 = strA[1].indexOf(")");
String min = strA[0].substring(i1+1);
String max = strA[1].substring(0,i2);
return getRandom(Integer.valueOf(min), Integer.valueOf(max)).toString();
}
if(str.contains("CurrentDate")){
return getCurrentDate();
}
if(str.contains("SecondTime")){
return getSecondTime().toString();
}
if(str.contains("MicTime")){
return getMicTime().toString();
}
if(str.contains("CardID")){
return getCardID();
}
if(str.contains("Phone")){
return getPhone();
}
return str;
}
4.3.6 專家模式,對於目前界面暫時無法支撐的個性化需求,我們采取通過在后端服務編碼的方式進行處理
@RequestMapping(value = {"/test"})
public void loadNum(HttpServletRequest request, HttpServletResponse httpServletResponse){
TimerTool timerTool = new TimerTool();
RestfulRequest restfulRequest = requestService.analyseRequest(request);
restfulRequest = filterHeader(restfulRequest);
log.info("分析請求cost:{}", timerTool.cut());
List<MockInfo> mockRules = mockInfoService.getMockRules(restfulRequest.getPath());
log.info("獲取mock配置記錄:{}, cost:{}", mockRules, timerTool.cut());
MockInfo mockRule = mockService.matchRulesNew(restfulRequest, mockRules);
log.info("匹配tag...cost:{}", timerTool.cut());
//先走平台模板,如果匹配不到,走專家模式(代碼實現)的MOCK匹配邏輯
if(mockRule != null){
mockService.doResponseNew(mockRule, httpServletResponse);
}else {
mockExpertService.doResponse(restfulRequest, httpServletResponse);
}
}
專家模式實現個性化的mock邏輯
public void doResponse(RestfulRequest request, HttpServletResponse response){
try {
RestfulBody body = request.getBody();
String ip = request.getIp();
JSONObject requestParams = body.getParams();
String funName = requestParams.getJSONObject("CMBSDKPGK").getJSONObject("INFO").getString("FUNNAM");
Integer responseStatus = 200 ;
Integer responseSleep = 0;
String responseString = "mock數據去火星了!";
String responseInfo = mockInfoService.getMockExpert(ip,funName);
JSONObject responseInfoJson;
if(StringUtils.isEmpty(responseInfo)){
responseInfoJson = new JSONObject(true);
responseInfoJson.put("count", 0);
responseInfoJson.put("firstTime", TimeUtils.nowTimeStamp() / 1000);
responseInfoJson.put("response", getJsonResponse("xmlResource/resp_" + funName + ".xml"));
}else {
responseInfoJson = JSONObject.parseObject(responseInfo);
}
int count = responseInfoJson.getInteger("count");
JSONObject responseObj = responseInfoJson.getJSONObject("response");
// 批量查詢余額
if("NTQADINF".equals(funName)){
long time_lag = (TimeUtils.nowTimeStamp() / 1000) - responseInfoJson.getLong("firstTime");
Object obj = responseObj.getJSONObject("CMBSDKPGK").get("NTQADINFZ");
// 距離第一次請求超過60s 余額+5000
if(time_lag > 60){
if(obj instanceof JSONArray){
JSONArray jsonA = (JSONArray)obj;
for(int i=0; i<jsonA.size();i++){
Double avlblv_value = jsonA.getJSONObject(i).getDoubleValue("AVLBLV");
avlblv_value = avlblv_value + 5000;
jsonA.getJSONObject(i).put("AVLBLV", String.format("%.2f",avlblv_value));
}
responseObj.put("NTQADINFZ", jsonA);
}
if(obj instanceof JSONObject){
JSONObject jsono = (JSONObject)obj;
Double avlblv_value = jsono.getDoubleValue("AVLBLV");
avlblv_value = avlblv_value + 5000;
jsono.put("AVLBLV", String.format("%.2f",avlblv_value));
responseObj.put("NTQADINFZ", jsono);
}
}
};
五、未來展望
目前這個版本我們適配了http協議下的大部分數據格式,如上所述,對於一些個性化的需求我們暫時采取的專家模式代碼實現,一方面體現了自有平台的靈活性,另一方面我們也會在不斷的使用過程中進行抽象提煉成共性的需求繼續可視化的實現。
內置函數隨着平台的使用范圍和深度,相信會有更多需求場景需要相關函數的實現加以滿足,不斷去豐富這些函數,也是平台健壯的一個表現;
另外對於其他相關協議的補充也是平台需要進行后續規划完善的發力點。
你們有哪些mock的實際使用場景,歡迎交流