背景
在研发或测试过程中,经常遇到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;
}
最终效果
初始界面
接口及结构体展示
选择测试环境并输入相应参数值返回测试结果
测试用例进行保存
以上就是该测试工具的实现过程,第一次在这写东西,如有不妥观者海涵!!