背景:开始一个应用的接口测试开发前,我们首先要搞清楚:待测应用到底有多少接口,同时在后续的维护中我们需要知道:新增了哪些接口、减少了哪些接口,这样方便进行用例的同步。
怎么去统计这些接口呢?
第一反应找开发讲解了下应用的代码和接口的所属模块后开始人肉统计,但是这样做无技术含量、重复劳动、冗余出错,那有没有办法通过工具去扫描接口。
先去百度看看有没有现成的轮子可以用,没有找到合适的工具,于是自己开始造轮子。
我们的需求是什么
需求:统计一个应用下面接口数。
接口是如何定义的?在我负责的应用代码中,有2个关键flag可以标识接口:
- 类有注解@WebResource
- 方法上有注解@ResourceMapping
代码
通过ClassLoader去实现接口代码统计:
- 一个简单的SpringBoot应用:
- 启动方法:
@EnableAutoConfiguration
public class Application {
/**
* @param args
* @throws MalformedURLException
*/
public static void main(String[] args) throws MalformedURLException {
String dir = "/xxx/target/classes/";
// 扫描目录下有哪些class文件,得到classname的列表
DirScanner dirScanner = new DirScanner();
List<File> nameLst = dirScanner.scan(dir);
List<String> classnameLst = new ArrayList<>();
for (File classFile : nameLst) {
String className = classFile.getAbsolutePath().replace(dir, "").replace("/", ".").replace(".class", "");
classnameLst.add(className);
}
// 加载class文件到jvm,扫描接口
CodeScanner codeScanner = new CodeScanner();
codeScanner.scan(dir, classnameLst);
}
}
- DirScanner源码:
作用:根据传入的路径递归扫描得到路径下所有文件的列表
public class DirScanner {
/**
* @return
*/
public List<File> scan(String dir) {
ArrayList<File> nameLst = new ArrayList<>();
File file = new File(dir);
if (!file.exists()) {
return nameLst;
}
if (!file.isDirectory()) {
nameLst.add(file);
return nameLst;
}
File[] files = file.listFiles();
for (File childFile : files) {
if (childFile.isDirectory()) {
nameLst.addAll(this.scan(childFile.getAbsolutePath()));
// System.out.println("0" + childFile.getName());
} else {
if (childFile.getName().endsWith(".class")) {
nameLst.add(childFile);
// System.out.println("1" + childFile.getName());
} else {
// System.out.println("2" + childFile.getName());
}
}
}
return nameLst;
}
}
- CodeScanner源码:
作用:将路径下的class文件加载到jvm,然后根据classname列表遍历扫描每个类的注解,返回符合条件的类-接口
public class CodeScanner {
Class methodAnnotationClass = ResourceMapping.class;
Class classAnnotationClass = WebResource.class;
public void scan(String dir, List<String> classnameLst) throws MalformedURLException {
ClassLoader systemclassloader = ClassLoader.getSystemClassLoader();
URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file://" + dir)}, systemclassloader);
for (String className : classnameLst) {
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(className);
} catch (Exception ex) {
System.out.println("error classname: " + className);
continue;
}
if (clazz.isAnnotationPresent(classAnnotationClass)) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(methodAnnotationClass)) {
System.out.println(className + " | " + method.getName() + " | " + ((ResourceMapping) method.getAnnotation(methodAnnotationClass)).value() + " | ");
}
}
}
}
}
}
- 注意事项(实际调试中踩的坑):
1.URLClassLoader初始化时传入的URL必须是包路径的上一层目录(例如:/xxx/target/classes),然后loadClass时必须要传入类的全名(package.classname),否则在调用loadClass时会报错:ClassDefNotFoundException,具体原因可以百度“URLClassLoader加载类的路径”相关说明。
2.在扫描工具的pom中需要加入被扫描应用的依赖,否则也会报错:ClassDefNotFoundException,这是因为loadClass时会去执行class文件的import语句,如果import的类没有引入,就会报错。
3.如果被扫描应用是多模块的应用,模块间有相互依赖,建议将应用的各个模块install到本地仓库,运行以下命令即可:
mvn clean install -Dmaven.skip.test=true
工具后续如何优化:
1.将接口的匹配规则提取出来,作为用户定义/参数传入。
2.代码中硬编码需要优化,转为可配置参数。-
扫描结果:
拷贝到Excel简单处理下就能得到各个类的接口数量。
相比人肉统计的好处在于:
1.可以反复统计,后续待测应用有新增/删除接口,扫描一把和历史结果对比下就得到结果了
2.速度快,比人肉统计准确