美文网首页
基于Java注解实现简易测试框架

基于Java注解实现简易测试框架

作者: halfempty | 来源:发表于2019-04-18 13:46 被阅读0次

    1. 前言

    看了点java注解, 心血来潮想撸个简易测试框架
    虽然已经有JUnit / Testng这般优秀的框架存在, 但就是头铁, 觉得自己还行
    于是便有了此番重复造轮子的经历, 还是破轮子

    2. 构思

    流程大体如下:

    1. 实例化reporter用于记录执行结果
    2. 扫描包目录并加载class
    3. 基于@Case注解, 提取class中的测试方法
    4. 基于order属性调整测试方法的排列顺序
    5. 按顺序执行测试方法, 将结果写入reporter
    6. 打印reporter

    3. 包扫描

    指定case所在的包名, 如com.lion.testcase, 或者通过入口类获取包名Application.class.getPackage().getName(), 遍历包目录, 收集所有class

        private void collectClasses(String dotPkgPath) {
            if (dotPkgPath.endsWith("."))
                dotPkgPath = dotPkgPath.substring(0, dotPkgPath.length() - 1);
    
            String pkgPath = dot2path(dotPkgPath);
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            URL url = loader.getResource(pkgPath);
    
            if (!url.getProtocol().equals("file"))
                throw new RuntimeException("Only support local mode");
    
            try {
                String fullPkgPath = URLDecoder.decode(url.getFile(), "UTF-8");
                classes.clear();
                scanPkg(new File(fullPkgPath), dotPkgPath);
    
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    
        private void scanPkg(File pkgFile, String dotPkgPath) {
            if (!pkgFile.exists() || !pkgFile.isDirectory())
                return;
    
            File[] files = pkgFile.listFiles();
            for (File f : files) {
                if (f.isDirectory()) {
                    scanPkg(f, dotPkgPath + "." + f.getName());
                    continue;
                }
    
                String className = trimClassSuffix(f.getName());
                String fullClassName = dotPkgPath + "." + className;
                classes.put(fullClassName, null);
            }
        }
    

    4. Case注解

    为了简单, 暂时只添加order属性, 控制用例执行的先后顺序
    后续为了加强框架的能力, 可以补充额外属性, 如:

    • group, 用例分组
    • enable, 控制用例是否执行
    • depend, 用例依赖
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Case {
        /**
         * cases 执行顺序, 值越小优先级越高
         * @return
         */
        public int order() default 0;
    }
    

    5. Case收集

    通过反射, 获取class下的所有方法, 如果方法标记有Case注解, 则认为是条有效用例

        private void collectCases(String className) {
            try {
                Class<?> clz = Class.forName(className);
                Method[] methods = clz.getMethods();
                for (Method method : methods) {
                    Annotation[] annotations = method.getDeclaredAnnotations();
    
                    for (Annotation annotation : annotations) {
                        if (annotation.annotationType().equals(Case.class)) {
                            CaseBean bean = new CaseBean();
                            bean.setClassname(className);
                            bean.setOrder(((Case) annotation).order());
                            bean.setMethod(method);
                            caseBeans.add(bean);
                            break;
                        }
                    }
                }
            } catch (ClassNotFoundException e) {
                return;
            }
        }
    

    以下为case样例

    public class AddTest {
    
        @Case
        public void test1Add1Eq3() {
            Expect.is(1+1 == 3);
        }
    
        @Case(order = 1)
        public void test1Add2Eq3() {
            Expect.is(1+2 == 3);
        }
    }
    
    

    6. 基于order排序

            Collections.sort(scanner.getCaseBeans(), new Comparator<CaseBean>() {
                @Override
                public int compare(CaseBean o1, CaseBean o2) {
                    return o2.getOrder() - o1.getOrder();
                }
            });
    

    7. 校验手段

    如何判断用例的执行结果正确与否呢, 且执行失败不能影响到后续用例的执行?
    暂时只想到抛异常的方式, 通过捕捉异常到判断用户是否执行失败
    这里偷懒使用了RuntimeException, 最好还是定义自己的异常类

    public class Expect {
    
        public static void is(boolean actual) {
            if(!actual)
                throw new RuntimeException("校验失败");
        }
    }
    

    8. 执行

    通过newInstance()实例化class, 所以只对默认构造器有效
    执行method(即用例)时, 也暂不考虑参数

    for(CaseBean bean: scanner.getCaseBeans()) {
        String className = bean.getClassname();
        if(!reporter.getResult().containsKey(className)) {
            reporter.getResult().put(className, new HashMap<String, CaseStatus>());
        }
    
        Method method = bean.getMethod();
        try {
            Object obj = scanner.getClasses().get(className);
            if(obj == null)
                obj = Class.forName(className).newInstance();
            method.invoke(obj);
            reporter.getResult().get(className).put(method.getName(), CaseStatus.OK);
        } catch (Exception e) {
            reporter.getResult().get(className).put(method.getName(), CaseStatus.FAILED);
        }
    }
    

    9. 结果打印

    根据需要, 自己定义report的结果集, 这里不展开
    后期可以丰富测试报告的呈现形式, 如使用html模板

    Start at: 1555565848507
    End at: 1555565849574
    Total time: 0.07 s
    
    com.lion.cases.CompareTest
        test1gt3    FAILED
        test10gt3   OK
    com.lion.cases.AddTest
        test1Add2Eq3    OK
        test1Add1Eq3    FAILED
    

    相关文章

      网友评论

          本文标题:基于Java注解实现简易测试框架

          本文链接:https://www.haomeiwen.com/subject/xwwzwqtx.html