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

TestNG如何在测试套件执行完成后重跑失败测试用例?

实现TestNG套件执行后统一重试失败用例的方案

当然可以搞定!你提到的并行负载导致的失败,确实不适合立即重试,套件跑完后统一重试才是更合理的选择。下面给你两种基于TestNG扩展机制的方案,完全不需要单独跑testNG-failed.xml,还能避免生成多份报告的问题:

方案一:用IExecutionListener + ITestListener收集并重试失败用例

这个思路是先在测试执行过程中收集所有失败的用例,等整个套件跑完后,动态创建TestNG实例来执行这些失败用例,并且让重试结果合并到原报告中。

步骤1:创建失败用例收集监听器

这个监听器负责捕获所有失败的测试方法,记录它们的类名和方法名:

import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;

public class FailedTestCollector implements ITestListener {
    // 用CopyOnWriteArrayList保证多线程下的安全
    private static final List<FailedTestInfo> failedTests = new CopyOnWriteArrayList<>();

    @Override
    public void onTestFailure(ITestResult result) {
        // 记录失败用例的类名和方法名
        failedTests.add(new FailedTestInfo(
            result.getTestClass().getName(),
            result.getMethod().getMethodName()
        ));
    }

    // 提供获取和清理失败用例的方法
    public static List<FailedTestInfo> getFailedTests() {
        return failedTests;
    }

    public static void clearFailedTests() {
        failedTests.clear();
    }

    // 内部类存储失败用例信息
    public static class FailedTestInfo {
        private final String className;
        private final String methodName;

        public FailedTestInfo(String className, String methodName) {
            this.className = className;
            this.methodName = methodName;
        }

        public String getClassName() { return className; }
        public String getMethodName() { return methodName; }
    }
}

步骤2:创建套件级重试监听器

这个监听器会在整个测试套件执行完成后,读取收集到的失败用例,动态创建TestNG任务执行重试:

import org.testng.IExecutionListener;
import org.testng.TestNG;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlInclude;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;
import java.util.List;

public class SuiteLevelRetryExecutor implements IExecutionListener {

    @Override
    public void onExecutionFinish() {
        List<FailedTestCollector.FailedTestInfo> failedTests = FailedTestCollector.getFailedTests();
        if (failedTests.isEmpty()) {
            System.out.println("没有失败用例需要重试");
            return;
        }

        // 初始化TestNG实例用于重试
        TestNG retryTestNG = new TestNG();
        XmlSuite retrySuite = new XmlSuite();
        retrySuite.setName("RetryFailedTests");

        XmlTest retryTest = new XmlTest(retrySuite);
        retryTest.setName("RetryFailedTestCases");

        // 将所有失败用例添加到重试套件中
        for (FailedTestCollector.FailedTestInfo info : failedTests) {
            XmlClass xmlClass = new XmlClass(info.getClassName());
            xmlClass.getIncludedMethods().add(new XmlInclude(info.getMethodName()));
            retryTest.getXmlClasses().add(xmlClass);
        }

        // 关键:设置和原测试相同的报告输出目录,合并报告
        retryTestNG.setOutputDirectory("test-output");
        // 如果用了并行,这里可以根据需要设置线程数,比如单线程重试
        retryTestNG.setThreadCount(1);
        retryTestNG.setXmlSuites(List.of(retrySuite));

        // 执行重试
        retryTestNG.run();

        // 清理收集的失败用例,避免影响后续执行
        FailedTestCollector.clearFailedTests();
    }
}

步骤3:在TestNG配置中注册监听器

在你的testng.xml中添加这两个监听器:

<suite name="YourMainSuite" parallel="tests" thread-count="20">
    <listeners>
        <listener class-name="com.yourpackage.FailedTestCollector"/>
        <listener class-name="com.yourpackage.SuiteLevelRetryExecutor"/>
    </listeners>
    <!-- 你的测试类/方法配置 -->
</suite>

注意事项

  • 报告合并:通过设置相同的outputDirectory,TestNG的默认报告会自动更新,重试的结果会追加到原报告中;如果用了第三方报告框架(比如ExtentReports),可以在监听器中共享同一个报告实例,实现无缝合并。
  • 线程安全:用CopyOnWriteArrayList存储失败用例,避免多线程并行执行时的并发问题。

方案二:改造RetryAnalyzer实现延迟重试

如果你想复用现有的RetryAnalyzer逻辑,可以改造它,让第一次失败时不立即重试,而是标记为需要延迟重试,等套件结束后统一处理。

步骤1:改造RetryAnalyzer

import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

public class DelayedRetryAnalyzer implements IRetryAnalyzer {
    // 用ConcurrentHashMap保证多线程安全
    private static final Map<String, Boolean> needRetry = new ConcurrentHashMap<>();

    @Override
    public boolean retry(ITestResult result) {
        String testKey = result.getTestClass().getName() + "." + result.getMethod().getMethodName();
        // 第一次失败时,标记需要重试,不立即执行
        if (!needRetry.containsKey(testKey)) {
            needRetry.put(testKey, true);
            return false; // 不立即重试
        }
        return false; // 后续不再处理,交给套件监听器
    }

    public static Map<String, Boolean> getNeedRetryTests() {
        return needRetry;
    }

    public static void clearNeedRetryTests() {
        needRetry.clear();
    }
}

步骤2:配置重试执行逻辑

和方案一类似,在SuiteLevelRetryExecutor中读取needRetry中的用例,执行重试即可。另外,你需要给所有测试方法添加@Test(retryAnalyzer = DelayedRetryAnalyzer.class),或者通过IAnnotationTransformer全局设置这个重试器,避免逐个方法配置。

两种方案对比

  • 方案一:无需修改现有测试方法,逻辑清晰,适合大多数场景。
  • 方案二:复用RetryAnalyzer的标记逻辑,但需要额外配置重试器,适合已经大量使用RetryAnalyzer的项目。

内容的提问来源于stack exchange,提问作者Nikunj Aggarwal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:15:33