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
相关产品推荐
相关产品推荐

