测试工具之基于Dubbo的http自动测试工具
白羽 2018-05-18 来源 :网络 阅读 1333 评论 0

摘要:本文将带你了解基于Dubbo的http自动测试工具,希望对大家学测试工具有所帮助


  测试需要配合写消费者的代码


  对于开发来说,倒是挺省劲。但是对于测试来说就有点麻烦了, 每次还要去写dubbo的消费程序,而且每次新增一个接口,都需要重新改写程序,费时费力。


  接口返回的结果无法定制

  由于我这边是做一些商品的推荐,每次结果的类型都是相同的,只是内部的算法不同。不过接口只是返回id,无法直观的判断商品相似程度或者用户的偏好程度,需要一个可视化的返回结果界面。

  于是在这种需求下,我设想了一个小程序,它可以满足下面的功能:

  1、测试可以根据测试需要,在界面自动选择请求的class和方法

  2、开发完成后,测试界面自动扫描出dubbo的提供者的class和对应的方法

  3、返回结果自动请求对应的图片和文字说明

  提前放一个效果图:测试工具之基于Dubbo的http自动测试工具

 

 1、扫描某个包下所有的类


  小程序开始的第一步就是需要扫描某个包下所有的dubbo实现类。


  由于工程是springboot,因此最终部署是在jar中。这时,就需要面临两个问题,如果是在开发工具中,如何获取包下的所有类;如果是在jar中,如何获取包下所有的类。


  首先通过classloader可以加载特定路径下的所有URL:

Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
//如果是jar,则采用JarURLConnection的方式连接,获得class文件
if("jar".equals(url.getProtocol())){
findClassesInJar(url,classes,pack);
}else{
findClassesInSrc(url,classes,pack);
}
}

   

  在工程中


  在工程中,class其实是以目录形式存放在本地的,直接按照file的方式遍历扫描class文件就行了:


public static void findClassesInSrc(URL url, Set<Class<?>> classes, String basePackage) throws UnsupportedEncodingException {
File dir = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
if (!dir.exists() || !dir.isDirectory()) {
return;
}
Arrays.stream(dir.listFiles())
.forEach(file -> {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
classes.add(Thread.currentThread().getContextClassLoader().loadClass(basePackage + '.' + className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
});
}

   

  在jar包中


  jar包是一种特殊的压缩包,java提供了JarFile类的entries方法,可以遍历jar中所有的文件。不过这里就没有目录或者文件的区别了,因为都是一个zip包中的资源而已。因此最后需要针对报名进行一下过滤:


public static void findClassesInJar(URL url,Set<Class<?>> classes,String basePackage) throws IOException, ClassNotFoundException {
//转换为JarURLConnection
JarURLConnection connection = (JarURLConnection) url.openConnection();
if (connection != null) {
JarFile jarFile = connection.getJarFile();
if (jarFile != null) {
//得到该jar文件下面的类实体
Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
while (jarEntryEnumeration.hasMoreElements()) {
JarEntry entry = jarEntryEnumeration.nextElement();
String jarEntryName = entry.getName();
//这里我们需要过滤不是class文件和不在basePack包名下的类
if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/",".").startsWith(basePackage)) {
String className = jarEntryName
.substring(0, jarEntryName.lastIndexOf("."))
.replace("/", ".");
classes.add(Thread.currentThread().getContextClassLoader().loadClass(className));
}
}
}
}
}

   

  2、扫描某个class下所有的方法


  获得某个类的所有方法

  然后通过反射可以直接通过class的名字,拿到它的所有方法,这些方法里面包含了一些通用的方法,如wait,notify等,需要给过滤掉。


public static Set<String> NORMAL_METHODS = new HashSet<>(Arrays.asList("wait","equals","toString","hashCode","getClass","notify","notifyAll"));
public static List<Method> getMethod(String className){
try {
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
Method[] methods = clazz.getMethods();
return Arrays.stream(methods)
.filter(method -> !NORMAL_METHODS.contains(method.getName()))
.collect(Collectors.toList());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return new ArrayList<>();
}

   

  这里需要注意,两个不同参数的方法,虽然名字相同,但是他们的parameterTypes是不同的。因此这里最好直接返回method,把name和parameterTypes一同作为结果返回。因为最终invoke的时候,还得通过参数类型把所有的参数都转换类型一下。


 3、方法的执行


  第三个难点,就是前端传过来的参数都是字符串,比如:

  com.xingoo.test.Provider1Impl 是对应的class

  test1 是对应的方法

  100 是对应的参数

  java.lang.Long 是参数对应的类型

  怎么能把请求通过正确的dubbo provider执行呢?——答案 就是Bean

  因为在Spring的项目中,dubbo的provider都是一个单例的bean。因此可以直接通过applicationContext获得对应的bean,只要保证bean的名字能规律的映射过来就行。

  可以参考下面的获取bean的方法:

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
// 非@import显式注入,@Component是必须的,且该类必须与main同包或子包
// 若非同包或子包,则需手动import 注入,有没有@Component都一样
// 可复制到Test同包测试
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtils.applicationContext == null){
SpringUtils.applicationContext  = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}

   

  在真正的实现类上,需要指定bean的名字:


@Service("Provider1Impl")
public class Provider1Impl implements ProviderApi {
...
}
然后利用反射,就可以执行这个bean的特定方法了:
// 反射拿到对应的class
Class cla = Thread.currentThread().getContextClassLoader().loadClass(clazz);
// 在appContext中拿到对应的bean
Object bean = SpringUtils.getBean(cla.getSimpleName());
// 格式化参数与参数类型
Class<?>[] parameterTypes = DubboApiUtils.paramTypeFormat(types);
Object[] parameters = DubboApiUtils.paramsFormat(params,types);
// 通过反射调用对应的方法
return cla.getMethod(method, parameterTypes).invoke(bean,parameters);

   

  对应参数处理的两个方法是:

/**
* 根据字符串拼接,获得对应的参数类型数组
* @param types
* @return
*/
public static Class<?>[] paramTypeFormat(String types){
List<Class<?>> paramsClasses = new ArrayList<>();
for(String type : types.split(",")){
try {
paramsClasses.add(Class.forName(type));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return paramsClasses.toArray(new Class[]{});
}
/**
* 根据参数类型,转换类型
* @param paramStr
* @param types
* @return
*/
public static Object[] paramsFormat(String paramStr,String types){
Class<?>[] classes = paramTypeFormat(types);
List<Object> formats = new ArrayList<>();
String[] params = paramStr.split(",");
for(int i =0;i<classes.length; i++){
//todo 简单粗暴,有其他的需要再加吧
if("Long".equals(classes[i].getSimpleName())){
formats.add(Long.valueOf(params[i]));
}else{
formats.add(params[i]);
}
}
return formats.toArray();
}

   

  4、商品自动请求描述信息


  最后就是jquery基于ajax请求,查询对应的接口结果就行了。需要注意ajax的同步问题:

$.ajax({
type : "post",
url : "xxxxx",
data : {xxx:xxx},
async : false,
success : function(r){
//todo
}
});

  总结


  总结来说,下面是遇到的问题和简单的对应办法:

  1、如何扫描工程或者普通web项目 某个包下的class——通过classloader获得路径,直接遍历file即可

  2、如何扫描jar中某个包下的class——通过JarFile获得对应的JarEntry

  3、如何获取Spring Boot中的Bean——通过实现ApplicationContextAware接口,获取applicationContext的引用

  4、如何动态执行某个对象的特定方法——基于反射method.invoke,需要注意传入的参数与类型问题

  通过这样一个小工具,又对反射有了更进一步的了解。


   本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标软件测试之测试工具频道!


本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程