「工具」Dubbo可视化测试工具的设计和实现

背景


在研发或测试过程中,经常遇到RPC接口的测试,为此我们写了大量的单元测试用例侵入在系统工程中繁琐的创建接口和测试数据占用了大量的时间为了提高测试效率,开发了FreeFly-Remote-API系统,该系统旨在用通过简单的操作方式实现 dev,qa 甚至online的RPC测试,来释放开发和测试人员的双手。

设计目标


1、无侵入性
严格保证系统独立,且拒绝在任何RPC业务服务中有嵌入相关的代码

2、易用性
可视化界面简单,配置简单,能够让任何开发和测试人员便捷使用

3、高效性
能够快速返回待查询接口相关信息,并利用缓存进行数据临时存储

设计思路图示


业务流转图示

业务步骤和项目技术栈


整个过程比较简单,具体流程如下描述:

1、通过输入pom依赖配置获取相关及依赖jar包,并下载到服务本地
2、通过java反射和类加载对jar包进行接口信息解析
3、前端按照解析结构输入参数,并从zookeeper拿取服务地址
4、远程请求获取数据集展示。

主要技术栈:

maven依赖:wagon-ssh,aether (具体描述见下方依赖说明)
java: 反射,类加载
注册中心:zookeeper
RPC服务框架:dubbo
前端结构暂时:jsTree


项目主要代码描述:

1.pom依赖信息

     <dependency>
            <groupId>org.apache.maven.wagon</groupId>
            <artifactId>wagon-ssh</artifactId>
            <version>${wagonVersion}</version>
      </dependency>
      <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-api</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-util</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-impl</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-connector-basic</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-transport-file</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-transport-http</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-transport-wagon</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-aether-provider</artifactId>
            <version>${mavenVersion}</version>
        </dependency>

【Aether用于在自己的应用中集成Mavne的功能,包括依赖计算、包的分发,对本地和远程仓库的访问,它设计时考虑了对各种类型的依赖包管理仓库的抽象,因此也可以进行扩展以支持其他类似的工具】
以上引述可以理解是对maven仓库功能的一个支持工具,在本系统中我们会用到该工具获取jar包版本,jar包依赖关系 和jar包下载的功能

jar包下载相关代码示例:[ jar包下载入参结构体]

public class MavenParams {
    
    /**
     * jar包在maven仓库中的groupId
     */
    private String groupId;
    /**
     * jar包在maven仓库中的artifactId
     */
    private String artifactId;
    /**
     * jar包在maven仓库中的version
     */
    private String version;

    /**
     * 登录远程maven仓库的用户名,若远程仓库不需要权限,设为null,默认为null
     */
    private String username=null;
    /**
     * 登录远程maven仓库的密码,若远程仓库不需要权限,设为null,默认为null
     */
    private String password=null;
    
    
    public MavenParams() {
        super();
    }



    public MavenParams(String groupId, String artifactId) {
        super();
        this.groupId = groupId;
        this.artifactId = artifactId;
    }
    
    public MavenParams(String groupId, String artifactId, String username,
                       String password) {
        super();
        this.groupId = groupId;
        this.artifactId = artifactId;
        this.username = username;
        this.password = password;
    }

    public MavenParams(String groupId, String artifactId, String version,
                       String username, String password) {
        super();
        this.groupId = groupId;
        this.artifactId = artifactId;
        this.version = version;
        this.username = username;
        this.password = password;
    }

    public String getGroupId() {
        return groupId;
    }
    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }
    public String getArtifactId() {
        return artifactId;
    }
    public void setArtifactId(String artifactId) {
        this.artifactId = artifactId;
    }
    public String getVersion() {
        return version;
    }
    public void setVersion(String version) {
        this.version = version;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

jar包下载相关代码示例:[ :获取符合要求的版本号]

    public  List<Version> getAllVersions(MavenParams params) throws VersionRangeResolutionException {
        String groupId = params.getGroupId();
        String artifactId = params.getArtifactId();
        RepositorySystem repoSystem = Booter.newRepositorySystem();
        RepositorySystemSession session = Booter.newRepositorySystemSession(repoSystem);
        Artifact artifact = new DefaultArtifact(groupId + ":" + artifactId + ":[0,)");
        VersionRangeRequest rangeRequest = new VersionRangeRequest();
        rangeRequest.setArtifact(artifact);
        rangeRequest.setRepositories(Booter.newRepositories(repoSystem, session));
        VersionRangeResult rangeResult = repoSystem.resolveVersionRange(session, rangeRequest);
        List<Version> versions = rangeResult.getVersions();
        return versions;
    }

jar包下载相关代码示例:[ 下载指定版本号的jar包]

 /**
     * 从指定maven地址下载指定jar包
     * @throws ArtifactResolutionException
     */
    public File DownLoad(MavenParams params) throws Exception {

        String groupId = params.getGroupId();
        String artifactId = params.getArtifactId();
        String version = params.getVersion();
        RepositorySystem repoSystem = Booter.newRepositorySystem();
        RepositorySystemSession session = Booter.newRepositorySystemSession(repoSystem);
        Artifact artifact = new DefaultArtifact(groupId + ":" + artifactId + ":" + version);
        //下载当前jar包
        File file = downJar(artifact, repoSystem, session);
        return file;
    }

  private static File downJar(Artifact artifact, RepositorySystem repoSystem, RepositorySystemSession session) throws ArtifactResolutionException {

        ArtifactRequest artifactRequest = new ArtifactRequest();
        artifactRequest.setRepositories(Booter.newRepositories(repoSystem, session));
        artifactRequest.setArtifact(artifact);
        repoSystem.resolveArtifact(session, artifactRequest);
        String basePath = session.getLocalRepository().getBasedir().getPath() + "/" + artifact.getGroupId().replace(".", "/") + "/" + artifact.getArtifactId() + "/" + artifact.getVersion();
        String jarName = artifact.getArtifactId() + "-" + artifact.getVersion() + ".jar";
        File file = FileSearch.findFiles(basePath, jarName);
        return file;
    }


 /**
     * 递归查找文件
     * @param baseDirName  查找的文件夹路径
     * @param targetFileName  需要查找的文件名
     */
    public static File findFiles(String baseDirName, String targetFileName) {

        File file = null;
        File baseDir = new File(baseDirName);       // 创建一个File对象
        if (!baseDir.exists() || !baseDir.isDirectory()) {  // 判断目录是否存在
            logger.info("文件查找失败:" + baseDirName + "不是一个目录!");
        }
        String tempName = null;
        //判断目录是否存在
        File tempFile;
        File[] files = baseDir.listFiles();
        for (int i = 0; i < files.length; i++) {
            tempFile = files[i];
            if(tempFile.isDirectory()){
                 file = findFiles(tempFile.getAbsolutePath(), targetFileName);
                if (file != null) {
                    return  file;
                }
            }else if(tempFile.isFile()){
                tempName = tempFile.getName();
                if(tempName.equals(targetFileName)){
                    return tempFile.getAbsoluteFile();
                }
            }
        }
        return file;
    }

jar包下载相关代码示例:[ 获取相关依赖的jar包]

private static List<Artifact> filterDependencyJar(Artifact artifact, RepositorySystem repoSystem, RepositorySystemSession session,   List<Artifact> dependencyJarNameList) throws Exception {

        ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
        descriptorRequest.setArtifact(artifact);
        descriptorRequest.setRepositories(Booter.newRepositories(repoSystem, session));
        ArtifactDescriptorResult descriptorResult = repoSystem.readArtifactDescriptor(session, descriptorRequest);
        for (Dependency dependency : descriptorResult.getDependencies()) {
            Artifact dependencyArtifact = dependency.getArtifact();
            String groupId = dependencyArtifact.getGroupId();
            //只获取有相关联关系的jar,第三方jar不在考虑范围
            if (groupId.startsWith("com.xxx") || groupId.startsWith("qd") || groupId.startsWith("platform")) { 
       
                if (dependencyJarNameList.contains(dependencyArtifact)) continue;
                dependencyJarNameList.add(dependencyArtifact);
                //递归获取内嵌的依赖包
                filterDependencyJar(dependencyArtifact,repoSystem,session,dependencyJarNameList);
            }
        }
        return dependencyJarNameList;
    }

dubbo服务请求代码示例:[ 注册中心初始化方法]

 private GenericService initDubboRegister(String interfaceName,String zkEnvKey){
        Map<String,String> registerUrlMap = ApiPropertiesClient.getRegisterUrl();
        // 当前应用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("remote-api-test");
        // 连接注册中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setProtocol("zookeeper");
        registry.setAddress(registerUrlMap.get(zkEnvKey));
        // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
        // 引用远程服务
        ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
        reference.setApplication(application);
        reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
        reference.setInterface(interfaceName);//"com.qding.member.service.IMemberAddressRpcService"
        reference.setGeneric(true);
        // 和本地bean一样使用xxxService
        GenericService genericService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重

        return genericService;
    }

dubbo服务请求代码示例: dubbo泛化调用

  public Object dubboInvokeByParam (String zkEnvKey,String interfaceName, String methodName,String[] pojoName, Object[] paramValueArray) {

        GenericService genericService =  initDubboRegister(interfaceName,zkEnvKey);
        //  用Map表示POJO参数,如果返回值为POJO也将自动转成Map
        //  如果返回POJO将自动转成Map
        Object result  = genericService.$invoke(methodName, pojoName, paramValueArray);
        return result;
    }

最终效果

初始界面

界面提供 jar包下载所需:groupId,artifactId,version配置输入界面

接口及结构体展示

指定接口请求响应结构解析

选择测试环境并输入相应参数值返回测试结果

测试返回结果集

测试用例进行保存

可对指定接口保存多个测试用例

以上就是该测试工具的实现过程,第一次在这写东西,如有不妥观者海涵!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,416评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 人的记忆是很有趣的一种东西、有些深埋在回忆里的人事物会在特定的时间地点忽然出现在眼前,就像是站在事件发生的地点唰地...
    Reika217阅读 357评论 0 0
  • 下午去参加了一次面试(嘘……先不要告诉我的同事和老板),面我的人是分管技术的集团副总,也是技术条线最大的脑袋了,虽...
    大谨阅读 520评论 0 2
  • 还未完全找到菲利大叔那洒脱的感觉,还需摸索。 每次都想画的像点,像点,再像点,继续努力
    922b647666be阅读 242评论 0 2