我是靠谱客的博主 高大黑米,最近开发中收集的这篇文章主要介绍扫描Junit单元测试中,没写断言的Case,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

在我们写单元测试时,有一些case永远也不会失败,我们称之为快乐的单元测试。

快乐的单元测试危害:
1. 起不到质量保证的作用,会误导开发者和代码维护者,错误的认为代码质量很高,场景覆盖很全。
2. 使得项目的方法覆盖率和行覆盖率看起来很高,其实是表面光鲜,内里败絮。

快乐的单元测试,其中一种写法就是 – 没有断言。 当项目测试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("rn");
}
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("rn");
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("rn");
}
}
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);
}
}

最后

以上就是高大黑米为你收集整理的扫描Junit单元测试中,没写断言的Case的全部内容,希望文章能够帮你解决扫描Junit单元测试中,没写断言的Case所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(37)

评论列表共有 0 条评论

立即
投稿
返回
顶部