程序化执行JUnit测试全部失败,请求排查依赖及代码问题
JUnit测试批量执行失败排查与修复
问题描述
我编写了如下TestRunner类代码,用于自动执行两个项目下的指定JUnit测试用例,但执行后所有测试(共5个)全部失败,与预期全通过的结果不符。虽未报依赖错误,但怀疑是缺失依赖导致,且已在POM中添加相关测试依赖,恳请帮忙排查并修改代码。
原代码
public class TestRunner { public static void main(String[] args) throws Exception { TestExecutionSummary summary1= runSingleTest("E:\\Tutorial\\proj1\\target\\test-classes","test.CFGTestTest"); TestExecutionSummary summary2= runSingleTest("E:\\Tutorial\\proj2\\target\\test-classes","test.ConfigTest"); CompareTestResult(summary1,summary2); } public static void CompareTestResult(TestExecutionSummary summary1,TestExecutionSummary summary2){ System.out.println("\n-----------------Compare Test Result-----------------"); if(summary1.getTestsSucceededCount()==summary2.getTestsSucceededCount()){ System.out.println("Test1 and Test2 are the same"); System.out.println("Passed: "+summary1.getTestsSucceededCount()+" Failed: "+summary1.getTestsFailedCount()); }else{ System.out.println("Test1 and Test2 are different"); System.out.println("Test1: "+"Passed: "+summary1.getTestsSucceededCount()+" Failed: "+summary1.getTestsFailedCount()); System.out.println("Test2: "+"Passed: "+summary2.getTestsSucceededCount()+" Failed: "+summary2.getTestsFailedCount()); } System.out.println("-----------------------------------------------------\n"); } /** * Execute a single test. * @param classPath The classpath * @param className The class name * @throws Exception Exceptions */ private static TestExecutionSummary runSingleTest(String classPath,String className) throws Exception { Class<?> testClass = getTestClassFromPath(classPath, className); assert testClass != null; return runTest(testClass); } /** * Get the test class from the classpath and class name. * @param classPath The classpath * @param className The class name eg: com.example.Test * @return The test class * @throws Exception Exceptions */ private static Class<?> getTestClassFromPath(String classPath,String className) throws Exception { // Check if the path exists File file = new File(classPath); if (!file.exists()) { System.out.println("Path does not exist"); return null; } URL url = file.toURI().toURL(); try (URLClassLoader classLoader = new URLClassLoader(new URL[]{url})) { return classLoader.loadClass(className); }catch (ClassNotFoundException e){ System.out.println("Class loading failed"+e.getMessage()); throw e; }catch (Exception e){ System.out.println("Exception"+e.getMessage()); throw e; } } /** * Run the test. * @param testClass The test class */ private static TestExecutionSummary runTest(Class<?> testClass) { System.out.println("Running tests for class " + testClass.getName()); Launcher launcher = LauncherFactory.create(); // Create a Launcher // Create a SummaryGeneratingListener SummaryGeneratingListener listener = new SummaryGeneratingListener(); // Create a LauncherDiscoveryRequest that includes the test class LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors(selectClass(testClass)) .build(); // Register the listener with the Launcher launcher.registerTestExecutionListeners(listener); launcher.execute(request); // Execute the tests // Get the execute summary return listener.getSummary(); } }
执行输出
Running tests for class test.CFGTestTest Running tests for class test.CFGTestTest -----------------Compare Test Result----------------- Test1 and Test2 are the same Passed: 0 Failed: 5 -----------------------------------------------------
问题分析与修复
核心问题
- 类路径不完整:仅加载
test-classes目录,未包含主项目编译类(target/classes)及第三方依赖jar,导致测试执行时无法找到依赖类,引发失败。 - 错误信息缺失:未打印测试失败的具体原因,无法定位问题根源。
- 测试类执行异常:输出显示两次执行同一测试类,推测是类加载失败后未正确提示,或参数传递有误。
修改后的代码
import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; public class TestRunner { public static void main(String[] args) throws Exception { TestExecutionSummary summary1 = runSingleTest( "E:\\Tutorial\\proj1\\target\\classes", "E:\\Tutorial\\proj1\\target\\test-classes", "test.CFGTestTest" ); TestExecutionSummary summary2 = runSingleTest( "E:\\Tutorial\\proj2\\target\\classes", "E:\\Tutorial\\proj2\\target\\test-classes", "test.ConfigTest" ); CompareTestResult(summary1, summary2); } public static void CompareTestResult(TestExecutionSummary summary1, TestExecutionSummary summary2) { System.out.println("\n-----------------测试结果对比-----------------"); if (summary1.getTestsSucceededCount() == summary2.getTestsSucceededCount()) { System.out.println("两个项目测试结果一致"); System.out.println("通过:" + summary1.getTestsSucceededCount() + " 失败:" + summary1.getTestsFailedCount()); } else { System.out.println("两个项目测试结果不一致"); System.out.println("项目1:通过:" + summary1.getTestsSucceededCount() + " 失败:" + summary1.getTestsFailedCount()); System.out.println("项目2:通过:" + summary2.getTestsSucceededCount() + " 失败:" + summary2.getTestsFailedCount()); } printFailureDetails(summary1, "项目1"); printFailureDetails(summary2, "项目2"); System.out.println("----------------------------------------------\n"); } private static void printFailureDetails(TestExecutionSummary summary, String projectName) { if (summary.getTestsFailedCount() > 0) { System.out.println("\n" + projectName + "失败测试详情:"); summary.getFailures().forEach(failure -> { System.out.println("测试方法:" + failure.getTestIdentifier().getDisplayName()); System.out.println("失败原因:" + failure.getException().getMessage()); System.out.println("堆栈追踪:"); failure.getException().printStackTrace(); System.out.println("----------------------------------------------"); }); } } private static TestExecutionSummary runSingleTest(String mainClassPath, String testClassPath, String className) throws Exception { Class<?> testClass = getTestClassFromPath(mainClassPath, testClassPath, className); assert testClass != null; return runTest(testClass); } private static Class<?> getTestClassFromPath(String mainClassPath, String testClassPath, String className) throws Exception { File mainFile = new File(mainClassPath); File testFile = new File(testClassPath); if (!mainFile.exists()) { System.out.println("主类路径不存在:" + mainClassPath); return null; } if (!testFile.exists()) { System.out.println("测试类路径不存在:" + testClassPath); return null; } List<URL> urls = new ArrayList<>(); urls.add(mainFile.toURI().toURL()); urls.add(testFile.toURI().toURL()); // 可选:添加Maven依赖jar,示例路径需替换为实际本地仓库路径 // urls.add(new File("C:\\Users\\xxx\\.m2\\repository\\org\\junit\\jupiter\\junit-jupiter-api\\5.9.2\\junit-jupiter-api-5.9.2.jar").toURI().toURL()); // urls.add(new File("C:\\Users\\xxx\\.m2\\repository\\org\\junit\\jupiter\\junit-jupiter-engine\\5.9.2\\junit-jupiter-engine-5.9.2.jar").toURI().toURL()); try (URLClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader())) { return classLoader.loadClass(className); } catch (ClassNotFoundException e) { System.out.println("类加载失败:" + e.getMessage()); throw e; } catch (Exception e) { System.out.println("异常:" + e.getMessage()); throw e; } } private static TestExecutionSummary runTest(Class<?> testClass) { System.out.println("正在执行测试类:" + testClass.getName()); Launcher launcher = LauncherFactory.create(); SummaryGeneratingListener listener = new SummaryGeneratingListener(); LauncherDiscoveryRequest request = request() .selectors(selectClass(testClass)) .build(); launcher.registerTestExecutionListeners(listener); launcher.execute(request); TestExecutionSummary summary = listener.getSummary(); System.out.println(testClass.getName() + "执行结果:通过" + summary.getTestsSucceededCount() + ",失败" + summary.getTestsFailedCount()); return summary; } }
关键修改点
- 补充完整类路径:同时加载主项目
target/classes和测试target/test-classes目录,确保测试类能找到依赖的主类。 - 新增失败详情打印:添加
printFailureDetails方法,输出失败测试的具体原因和堆栈,快速定位问题。 - 优化类加载器:使用当前线程上下文类加载器作为父类,避免依赖加载冲突或缺失。
- 增强错误提示:完善路径检查和异常信息输出,方便定位类加载或路径问题。
额外建议
- 先通过IDE或Maven命令
mvn test单独执行项目测试,确认测试用例本身可正常通过。 - 若为Maven项目,可执行
mvn dependency:build-classpath获取完整依赖路径,自动加载所有依赖jar,无需手动添加。
内容的提问来源于stack exchange,提问作者Wolf Asiv
相关产品推荐
相关产品推荐

