You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

程序化执行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
-----------------------------------------------------

问题分析与修复

核心问题

  1. 类路径不完整:仅加载test-classes目录,未包含主项目编译类(target/classes)及第三方依赖jar,导致测试执行时无法找到依赖类,引发失败。
  2. 错误信息缺失:未打印测试失败的具体原因,无法定位问题根源。
  3. 测试类执行异常:输出显示两次执行同一测试类,推测是类加载失败后未正确提示,或参数传递有误。

修改后的代码

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;
    }
}

关键修改点

  1. 补充完整类路径:同时加载主项目target/classes和测试target/test-classes目录,确保测试类能找到依赖的主类。
  2. 新增失败详情打印:添加printFailureDetails方法,输出失败测试的具体原因和堆栈,快速定位问题。
  3. 优化类加载器:使用当前线程上下文类加载器作为父类,避免依赖加载冲突或缺失。
  4. 增强错误提示:完善路径检查和异常信息输出,方便定位类加载或路径问题。

额外建议

  • 先通过IDE或Maven命令mvn test单独执行项目测试,确认测试用例本身可正常通过。
  • 若为Maven项目,可执行mvn dependency:build-classpath获取完整依赖路径,自动加载所有依赖jar,无需手动添加。

内容的提问来源于stack exchange,提问作者Wolf Asiv

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 03:47:02