IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 扫描Junit单元测试中,没写断言的Case -> 正文阅读

[开发测试]扫描Junit单元测试中,没写断言的Case

在我们写单元测试时,有一些case永远也不会失败,我们称之为快乐的单元测试。 这种case危害很大,不仅起不到质量保证的作用,还会误导开发者,以为自己的代码质量很高,场景覆盖很全。 也使得最终项目的方法覆盖率和行覆盖率都挺高,真真是表面光鲜,内里败絮。

快乐的单元测试,其中一种写法就是 – 没有断言。 当项目测试case很多,达到上千上万时,通过肉眼去找到它们难度很大,所以笔者写了一段代码,来识别没有断言的case。

思路如下:

参数为要扫描的包的路径。 通过路径先去找到该路径下,所有测试类的class文件。 对这class文件,逐一通过下面方法扫描:

  1. 使用 Class.forName() 加载该class文件。 再取出该类的注解, 如果有 @Ignore 注解,则忽略; 没有该注解则进行下一步;

  2. 获取该类下的所有方法, 如果该方法 没有@Ingore注解 && 有@Test注解 && @Test注解里的expeced为none(为none说明该case没有在注解里进行异常判断), 说明该方法是个需要写断言单元测试的case;

  3. 取出需要校验的方法后, 再使用 “javap -c [classPath]” 反编译该class文件, 取出反编译后该方法的内容, 如果不包含"org/junit/Assert." , 说明该case没有使用断言;

代码如下:

package com.tinyv.demo.test.util;


import org.junit.Ignore;
import org.junit.Test;
import org.junit.platform.commons.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author tiny_v
 * @date 2022/3/23.
 *
 * 功能: 检查快乐的单元测试,即没有断言的断言测试
 */
public class CheckHappyTest {

    private static Logger logger = LoggerFactory.getLogger(CheckHappyTest.class);


    private final String basePackage = "com.tinyv.demo.test";

    /**
     * 执行反编译命令, 返回值为指定方法的反编译内容
     * @param classPath
     * @return
     */
    private String getClassContent(String classPath){
        StringBuilder sb = new StringBuilder();
        String cmd = "javap -c "+classPath;
        try {
            //执行命令
            Process p = Runtime.getRuntime().exec(cmd);
            //获取输出流,并包装到BufferedReader中
            BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\r\n");
            }
            p.waitFor();
        } catch(IOException e){
            e.printStackTrace();
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        return sb.toString();
    }


    /**
     * 从类的反编译内容中,抽取指定方法的内容
     * @param methodName
     * @return
     */
    private String getMethodContent(String classContent, String methodName){
        StringBuilder sb = new StringBuilder();
        String[] lines = classContent.split("\r\n");
        boolean print = false;
        for(String line : lines){
            if(line.contains(methodName)){
                print = true;
            }
            if(StringUtils.isBlank(line)){
                print = false;
            }
            if(print){
                sb.append(line).append("\r\n");
            }
        }
        return sb.toString();
    }

    /**
     * 校验是否有Assert断言
     * @param mContent
     * @return
     */
    private boolean checkAssert(String mContent){
        try{
            return mContent.contains("invokestatic") && mContent.contains("org/junit/Assert.");
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 加载类
     * @param name
     * @return
     */
    private Class getBasicClass(String name) {
        Class clazz = null;
        try {
            clazz = Class.forName(name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return clazz;
    }

    /**
     * 获取需要校验的有没有断言的Case
     * 1. 如果类上加了@Ignore注解, 不需要校验
     * 2. 方法上没有@Test注解,或者加了@Ignore注解 或者 方法使用excepted进行异常断言, 不需要校验
     * @param classPath
     * @return
     */
    private ArrayList getJunitMethods(String classPath){
        Class clazz = getBasicClass(classPath);
        if(clazz==null){
            return null;
        }
        //如果类上加了@Ignore注解, 则认为不需要校验
        Annotation cIgnore = clazz.getAnnotation(Ignore.class);
        if(cIgnore!=null){
            return null;
        }
        Method[] methods = clazz.getMethods();
        if(clazz.getMethods()==null || clazz.getMethods().length==0){
            return null;
        }
        ArrayList<String> methodNames = new ArrayList();
        for (Method method : methods) {
            Annotation mAnnotation = method.getAnnotation(Test.class);
            Annotation mIgnore = method.getAnnotation(Ignore.class);
            Annotation noAssert = method.getAnnotation(NoAssert.class);
            //方法上加了@Test注解 && 方法没有使用excepted进行异常断言 && 方法没加@Ignore注解 -> 认为是需要校验的case
            if(mAnnotation!=null && mAnnotation.toString().contains("expected=class org.junit.Test$None)") && mIgnore==null && noAssert==null){
                methodNames.add(method.getName());
            }
        }
        return methodNames;
    }

    private List<String> getClassName(String packageName, boolean childPackage) {
        List<String> fileNames = null;
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageName.replace(".", "/");
        URL url = loader.getResource(packagePath);
        if (url != null) {
            String type = url.getProtocol();
            if (type.equals("file")) {
                fileNames = getClassNameByFile(url.getPath(), null, childPackage);
            }
        }
        return fileNames;
    }

    private List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {
        List<String> myClassName = new ArrayList<>();
        File file = new File(filePath);
        File[] childFiles = file.listFiles();
        for (File childFile : childFiles) {
            if (childFile.isDirectory()) {
                if (childPackage) {
                    myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage));
                }
            } else {
                String childFilePath = childFile.getPath();
                if (childFilePath.endsWith(".class")) {
                    myClassName.add(childFilePath);
                }
            }
        }
        return myClassName;
    }

    private void execute() {
        String split = basePackage.split("[.]")[0];
        try {
            List<String> files = getClassName(basePackage, Boolean.TRUE);
            int m_number = 0;
            for (String file : files) {
                String classPath = (split+file.split(split)[1]).replace("\\", ".").replace(".class", "");
                ArrayList<String> methodNames = getJunitMethods(classPath);
                if(methodNames==null || methodNames.size()==0){
                    continue;
                }
                String c_content = getClassContent(file);
                logger.info("===== 类名:[{}]",  classPath);
                for(String methodName : methodNames){
                    String m_content = getMethodContent(c_content, methodName);
                    if(!checkAssert(m_content)){
                        logger.info("========== No.{}, 方法名:[{}]", ++m_number, methodName);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        logger.info("============================== Start ===================================");
        long startTime = System.currentTimeMillis();
        new CheckHappyTest().execute();
        logger.info("============================== End ===================================");
        logger.info("============================== Total Cost: {} seconds", (System.currentTimeMillis()-startTime)/1000);
    }
    
}

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2022-04-01 00:24:13  更:2022-04-01 00:24:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/13 14:27:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码