赞
踩
在Spring Boot项目中,对Service类进行单元测试对于开发工程师而言具有重大意义和作用:
单元测试在Spring Boot项目中扮演着至关重要的角色,对于确保代码质量、加速开发过程、降低维护成本以及推动良好的开发实践具有显著影响。
由于所在公司的代码环境切换至内部网络,现有的插件用于生成单元测试变得不再适用。为了解决这一挑战,提高工作效率,我开发了一个单元测试生成Java工具类,专门用于自动生成服务类的单元测试代码。
代码框架:
依赖 | 版本 |
---|---|
Spring Boot | 2.7.12 |
JUnit | 5.8.2 |
我们的主要目标是创建一个尽可能完善的Spring Boot单元测试方法生成器,以减少重复工作并提高工作效率。
我们的工具类具备以下特点:
这个工具类的开发旨在提升测试代码的编写效率,同时保持测试覆盖率的完整性,从而避免在单元测试编写方面重复“造轮子”。
import java.io.*; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.util.*; public class TestClassAutoGenerator { // JAVA保留字 private static final List<String> keywords = Arrays.asList("abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while"); private static final String javatest = "/src/test/java/"; // 创建目录 public static void createDirectoryIfNeeded(String filePath) { File file = new File(filePath); File directory = file.getParentFile(); if (directory != null && !directory.exists()) { // 如果目录不存在,则创建它 boolean isCreated = directory.mkdirs(); if (isCreated) { System.out.println("目录已创建: " + directory.getAbsolutePath()); } else { System.out.println("目录创建失败: " + directory.getAbsolutePath()); } } else { assert directory != null; System.out.println("目录已存在: " + directory.getAbsolutePath()); } } // 主体方法:按service类在指定项目下自动生成service类 public void generateTestForClass(String outputPath, Class<?> serviceClass) { String packagePath = serviceClass.getPackage().getName().replace(".","/"); // 生成路径 outputPath = outputPath+javatest+packagePath; String className = serviceClass.getSimpleName(); String testClassName = className + "Test"; // 测试类的代码内容 String content = generateTestClassContent(serviceClass, testClassName); createDirectoryIfNeeded(outputPath + "/" + testClassName + ".java"); try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath + "/" + testClassName + ".java"))) { writer.write(content); } catch (IOException e) { e.printStackTrace(); } } // 测试类的代码生成 private String generateTestClassContent(Class<?> serviceClass, String testClassName) { StringBuilder classContent = new StringBuilder(); classContent.append("package ").append(serviceClass.getPackage().getName()).append(";\n\n"); // 导入请求响应包 Set<String> imports = new HashSet<>(); for (Method method : serviceClass.getDeclaredMethods()) { Class<?>[] paramTypes = method.getParameterTypes(); for (Class<?> paramType : paramTypes) { if (paramType.getPackage() != null && !imports.contains(paramType.getPackage().getName() + "." + paramType.getSimpleName())) { classContent.append("import ").append(paramType.getPackage().getName()).append(".").append(paramType.getSimpleName()).append(";\n"); imports.add(paramType.getPackage().getName() + "." + paramType.getSimpleName()); } } Class<?> returnType = method.getReturnType(); if (returnType.getPackage() !=null && !imports.contains(returnType.getPackage().getName()+"."+returnType.getSimpleName())) { classContent.append("import ").append(returnType.getPackage().getName()).append(".").append(returnType.getSimpleName()).append(";\n"); } } // 导入SpringBoot项目运行测试所需的包 classContent .append("import lombok.extern.slf4j.Slf4j;\n") .append("import ").append(serviceClass.getPackage().getName()).append(".").append(serviceClass.getSimpleName()).append(";\n") .append("import org.junit.jupiter.api.Test;\n") .append("import org.springframework.boot.test.context.SpringBootTest;\n") .append("import com.alibaba.fastjson.JSON;\n") .append("import org.springframework.beans.factory.annotation.Autowired;\n\n") .append("@Slf4j\n") .append("@SpringBootTest\n") .append("public class ").append(testClassName).append(" {\n\n") .append(" @Autowired\n") .append(" private ").append(serviceClass.getSimpleName()).append(" ") .append(toCamelCase(serviceClass.getSimpleName())).append(";\n\n"); // 遍历生成单元测试 for (Method method : serviceClass.getDeclaredMethods()) { if (Modifier.isPublic(method.getModifiers())) { classContent.append(" @Test\n") .append(" public void test").append(capitalizeFirstLetter(method.getName())) .append("() throws Exception {\n") .append(generateMethodTestLogic(method,serviceClass)) .append(" }\n\n"); } } classContent.append("}\n"); return classContent.toString(); } // 生成单元测试代码 private String generateMethodTestLogic(Method method,Class<?> serviceClass) { StringBuilder testLogic = new StringBuilder(); testLogic.append(" // Test logic for ").append(method.getName()).append("\n"); Class<?>[] paramTypes = method.getParameterTypes(); Class<?> returnType = method.getReturnType(); List<String> params = new ArrayList<>(); Hashtable<String, Integer> paramCount = new Hashtable<>(); for (Class<?> paramType : paramTypes) { String param = getParamName(paramType, paramCount); testLogic.append(" ").append(paramType.getSimpleName()).append(" ") .append(param).append("="); testLogic.append(getDefaultValueForType(paramType)); testLogic.append(";\n"); params.add(param); if (getDefaultValueForType(paramType).startsWith("new")) { testLogic.append(" //TODO set params for ").append(toCamelCase(paramType.getSimpleName())).append("\n\n"); } } testLogic.append(" "); if (returnType.getPackage()!=null) { testLogic.append(returnType.getSimpleName()).append(" response = "); } testLogic.append(toCamelCase(serviceClass.getSimpleName())) .append(".").append(method.getName()).append("("); for (int i = 0; i < paramTypes.length; i++) { testLogic.append(params.get(i)); if (i < paramTypes.length - 1) { testLogic.append(", "); } } testLogic.append(");\n"); if (returnType.getPackage()!=null) { testLogic.append(" log.info(\"Response: \" + JSON.toJSONString(response));\n"); } return testLogic.toString(); } private String getParamName(Class<?> paramType,Hashtable<String, Integer> paramCount) { String name = paramType.getSimpleName(); String init = "arg"; if (paramType.isPrimitive() ) { if (paramType.equals(boolean.class)) { init = "flag"; } } else if (paramType.equals(String.class)) { init = "s"; } else { init =toCamelCase(name); } if (keywords.contains(init)) { init =init.substring(0,1); } if (paramCount.get(init)==null) { paramCount.put(init,1); return init; } else { paramCount.replace(init,paramCount.get(init)+1); return init+(paramCount.get(init)); } } // 生成默认值 private String getDefaultValueForType(Class<?> type) { if (type.isPrimitive()) { if (type.equals(boolean.class)) { return "false"; } else if (type.equals(long.class)) { return "0L"; }else if (type.equals(float.class)) { return "0F"; }else if (type.equals(double.class)) { return "0D"; } return "0"; } else if (type.equals(String.class)) { return "\"\""; } else if (type.equals(Long.class)) { return "0L"; } else if (type.equals(Float.class)) { return "0F"; } else if (type.equals(Double.class)) { return "0D"; } else if (type.equals(Short.class) || type.equals(Integer.class)) { return "0"; } else if (type.equals(BigDecimal.class)) { return "new " + type.getSimpleName() + "(\"0\")"; } else if (type.isEnum()) { return type.getSimpleName()+"."+type.getEnumConstants()[0].toString(); } else { return "new " + type.getSimpleName() + "()"; } } private String toCamelCase(String str) { return Character.toLowerCase(str.charAt(0)) + str.substring(1); } private String capitalizeFirstLetter(String str) { return Character.toUpperCase(str.charAt(0)) + str.substring(1); } // 程序入口 public static void main(String[] args) { TestClassAutoGenerator generator = new TestClassAutoGenerator(); // 为单一类生成单元测试 generator.generateTestForClass("XX-app-service(换成你的单元测试所在项目名称)", XXService.class); } }
通过这些拓展,工具将更加智能化和自动化,能够更有效地适应复杂的测试环境和多样化的需求。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。