轉載:http://blog.csdn.net/ronghuanye/article/details/71124127
1 簡介
Dubbo目前的應用已經越來越廣泛、或者基於Dubbo二次開發的也越來越多,使用到Dubbo的系統基本也是采用微服務架構設計的系統,多個系統、多個應用之間的接口是有依賴關系的,所以就會出現需要MOCK的應用場景。
當我們發布了兩個應用A和B,應用A引用應用B發布的接口,那么我們的應用場景是MOCK應用B(接口提供方)。通常的MOCK方式是,測試人員自己發布一個一樣的接口(應用C),然后把應用A的調用指向(應用C),這種方式可以解決MOCK,但是這種方式的弊端:
1.需要開發MOCK代碼
2.對人員要求技能高
3.需要接口提供方的依賴jar
4.需要容器發布應用C
5.接口變更需要更新MOCK代碼
6.發布時間長,需要編碼發布
我們可以采取另外一個方式,通過添加dubbo的filter過濾器,通過過濾器攔截請求,把請求導向mock平台,或者攔截請求后直接返回已經緩存的響應數據,達到mock的效果而且可以配置返回的數據內容和響應時間。filter是放在消費端的(應用A),配置filter之后應用A的請求首先會到filter里面。
下面是添加filter后需要實現的工作流程圖:
具體的filter代碼實現請看下面的章節介紹!
RPC接口mock平台架構圖:
2 RPC接口mock平台在接口測試中的用處
2.1 屏蔽關聯接口依賴關系,快速定位接口的性能瓶頸點
當被測試接口依賴多個其他接口時,出現了性能問題后無法定位性能瓶頸是出在被測接口還是其他依賴的接口,這時可以使用接口mock平台屏蔽掉其他依賴的接口,這樣就可以很快的定位是被測接口存在性能問題,還是其他依賴接口存在性能問題。
2.2 用mock平台代替依賴服務的部署,提高環境部署的效率,減少環境部署的工作量。
當進行集群測試時,需要部署大量的其他依賴服務器,如果依賴的服務部署比較繁瑣,工作量較大時,可以考慮使用mock平台代替依賴服務的部署,既節省服務器資源又可提高部署環境的效率。
2.3 使用mock平台,可以方便查看被測試接口的依賴關系
從mock平台配置界面或者數據庫可以看到被測接口依賴的其他接口列表:
2.4 使用mock平台的配置界面可以方便配置接口的mock參數(返回數據,mock開關,響應時間)
可以在mock平台的前端配置界面配置接口的mock參數,例如返回數據,mock開關,響應時間等。
3 RPC接口mock測試平台使用方法
3.1 實現com.alibaba.dubbo.rpc.Filter接口
實現com.alibaba.dubbo.rpc.Filter接口的類CustomConsumerFilter 代碼如下:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import net.sf.json.JSONObject;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcResult;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
@SuppressWarnings("deprecation")
public class CustomConsumerFilter implements Filter {
public static Map<String, Object> map = new HashMap<String, Object>(); //保存接口對象
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result = null; //定義一個返回對象;
String interfaceName = invoker.getUrl().getPath(); //獲取接口服務名稱
String methodName = invocation.getMethodName(); //獲取接口方法名稱
Object[] arguments =invocation.getArguments(); //獲取參數集合
List<String> paramTypes = new ArrayList<String>(); //獲取參數類型
for(int i=0; i<arguments.length; i++)
{
paramTypes.add(arguments[i].getClass().getTypeName());
}
Method method = null;
try {
method = Class.forName(interfaceName)
.getDeclaredMethod(methodName, invocation.getParameterTypes()); //獲取調用方法
} catch (Exception e) {
e.printStackTrace();
}
Type returnType = method.getGenericReturnType(); //獲取返回類型
JSONObject responeJSON = new JSONObject();
if(map.containsKey(interfaceName + "+" + methodName + "+" + paramTypes.toString()))
{
responeJSON = (JSONObject) map.get(interfaceName + "+" + methodName + "+" + paramTypes.toString());
if(Integer.parseInt(responeJSON.getString("status"))==1)
{
String returnStr = responeJSON.getString("returnJson");
Object returnJson = null; //定義響應對象
try {
returnJson = JSON.parseObject(returnStr,Class.forName(returnType.getTypeName()));
} catch (Exception e) {
e.printStackTrace();
}
result = new RpcResult(); //定義返回結果
((RpcResult) result).setResult(returnJson);
((RpcResult) result).setException(null);
}
else {
result = invoker.invoke(invocation); //調用真實接口
}
}
else {
String url = "http://mock.vmall.com/tcep/"; //mock服務器地址
JSONObject jsonObj = new JSONObject();
String paramJson =JSON.toJSONString(arguments, SerializerFeature.WriteClassName); //序列化入參為JSON串
//請求測試平台查詢接口mock信息
jsonObj.put("interfaceName", interfaceName);
jsonObj.put("methodName", methodName);
jsonObj.put("paramTypes", paramTypes.toString());
jsonObj.put("paramJson", paramJson);
jsonObj.put("returnType", returnType.getTypeName());
String resp = httpPost(url + "mock!goMock.action", jsonObj.toString());
responeJSON = JSONObject.fromObject(resp);
if(responeJSON.getBoolean("success"))
{
String mess = responeJSON.getString("message");
if(!mess.equals(""))
{
JSONObject responeJSON2 = JSONObject.fromObject(mess);
map.put(interfaceName + "+" + methodName + "+" + paramTypes.toString(), responeJSON2);
if(Integer.parseInt(responeJSON2.getString("status"))==1)
{
String returnStr = responeJSON2.getString("returnJson");
Object returnJson = null; //定義響應對象
try {
returnJson = JSON.parseObject(returnStr,Class.forName(returnType.getTypeName()));
} catch (Exception e) {
e.printStackTrace();
}
result = new RpcResult(); //定義返回結果
((RpcResult) result).setResult(returnJson);
((RpcResult) result).setException(null);
}
else {
result = invoker.invoke(invocation); //調用真實接口
}
} else {
result = invoker.invoke(invocation); //調用真實接口
//然后把接口信息存到測試平台
jsonObj.put("returnJson", JSON.toJSONString(result.getResult()));
httpPost(url + "mock!addMock.action", jsonObj.toString());
}
}
}
return result;
}
/*
* 發送Http post請求
*/
@SuppressWarnings("finally")
public String httpPost(String url,String jsonParam){
DefaultHttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(url);
HttpResponse response = null;
String respStr = "";
try {
StringEntity entity = new StringEntity(jsonParam,"utf-8");
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
post.setEntity(entity);
response = client.execute(post);
//獲取響應數據
InputStreamReader reader = new InputStreamReader(response.getEntity().getContent(),"UTF-8");
BufferedReader br = new BufferedReader(reader);
String line = null;
StringBuilder sb = new StringBuilder();
while((line = br.readLine())!=null){
sb.append(line);
}
respStr = sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
finally {
client.close();
return respStr;
}
}
}
Filter類的java工程如下:
注意:需要在工程中創建目錄META-INF/dubbo,然后在目錄中創建文件com.alibaba.dubbo.rpc.Filter,文件內容添加
CustomConsumerFilter=com.huawei.vmall.mock.CustomConsumerFilter
將上面的java類打成jar包
導出的jar包如下所示:
3.2 Cosumer端添加filter過濾器,加入依賴包,配置hosts映射地址
1、添加filter過濾器
在被測系統中找到dubbo配置文件,tomcat部署的應用一般是
webapps/ROOT/WEB-INF/classes/spring目錄中的dubbo-context.xml
Jar包啟動的應用一般是lib/conf/spring目錄中的dubbo-context.xml
找到consumer配置,在filter中添加CustomConsumerFilter參數:
2、加入依賴包
filter類需要依賴的jar包如下:
在被測系統的lib目錄中添加filter類依賴的jar和上一步導出的filter類jar包,filter類依賴的前面4個jar包一般都會已經存在lib目錄中了,不需要再重新添加,后面3個jar如果已經存在則不需要添加,不存在則添加。
Tomcat方式啟動的應用lib目錄
Jar包方式啟動的應用lib目錄
注意:添加jar前先檢查是否已經存在,版本不同也沒有關系,如果已經存在則不需添加。
3、配置hosts映射地址
在被測系統服務器中添加hosts配置
其中10.41.150.52是mock服務器的ip地址,mock.vmall.com是mock服務器的域名,域名已經寫死在filter類中不能改,ip地址可以根據實際部署ip進行修改。
3.3 調試被測接口,添加接口mock
添加完成filter和jar包后需要重新啟動被測系統,然后在eclipse中調試RPC接口,調試成功后會在mock數據庫中添加依賴的接口信息。
Mock數據庫插入的數據,其中status為0表示沒有開啟mock,需要手工在界面或者數據庫中改1才能開啟mock;要保證接口返回數據的正確性,可以在界面或者數據庫中修改returnJson字段修改返回數據。
3.4 驗證接口mock是否正常
當開啟了接口mock之后,在jmeter中執行接口測試場景,驗證接口mock是否正常,需要查看jmeter是否報錯,被測系統的日志是否報錯,查看接口調用日志中調用依賴接口的響應時間是否為0或者為預定的值。
4 常見問題
1、調用RPC接口時日志報下面的錯誤
解決辦法:
由於缺少以下相關依賴包
dubbo-2.5.3.jar,fastjson-1.1.15.jar,httpclient-4.5.2.jar,httpcore-4.4.4.jar,json-lib-2.4-jdk15.jar,ezmorph-1.0.6.jar,commons-collections-3.2.1.jar,將依賴包加入被測試系統的lib目錄下就可以了(先檢查一下lib目錄下面是否已經存在相關jar包,版本不同也沒有關系,如果已經存在則不需添加)