「工具」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配置輸入界面

接口及結構體展示
指定接口請求響應結構解析

選擇測試環境並輸入相應參數值返回測試結果
測試返回結果集

測試用例進行保存
可對指定接口保存多個測試用例

以上就是該測試工具的實現過程,第一次在這寫東西,如有不妥觀者海涵!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章