Remove (duplicate) failed TestNG result via test listener

后端 未结 3 1182
我在风中等你
我在风中等你 2021-01-13 06:50

Similar to the solution posted here TestNG retrying failed tests doesn't output the correct test results, I\'m trying to remove a (duplicate) test result using a test li

相关标签:
3条回答
  • 2021-01-13 07:21

    Try using this code:

    ListenerApadter:

    public class MyTestListenerAdapter extends TestListenerAdapter {
        @Override
        public void onTestFailure(ITestResult result) {
            if (result.getMethod().getRetryAnalyzer() != null) {
                MyRetryAnalyzer retryAnalyzer = (MyRetryAnalyzer)result.getMethod().getRetryAnalyzer();
    
                if(retryAnalyzer.isRetryAvailable()) {
                    result.setStatus(ITestResult.SKIP);
                } else {
                    result.setStatus(ITestResult.FAILURE);
                }
                Reporter.setCurrentTestResult(result);
            }
        }
    
       @Overrride
       public void onFinish(ITestContext context) {
         Iterator<ITestResult> failedTestCases =context.getFailedTests().getAllResults().iterator();
        while (failedTestCases.hasNext()) {
            System.out.println("failedTestCases");
            ITestResult failedTestCase = failedTestCases.next();
            ITestNGMethod method = failedTestCase.getMethod();
            if (context.getFailedTests().getResults(method).size() > 1) {
                System.out.println("failed test case remove as dup:" + failedTestCase.getTestClass().toString());
                failedTestCases.remove();
            } else {
    
                if (context.getPassedTests().getResults(method).size() > 0) {
                    System.out.println("failed test case remove as pass retry:" + failedTestCase.getTestClass().toString());
                    failedTestCases.remove();
                }
            }
        }
       }
    }
    

    RetryAnalizer:

    public class MyRetryAnalyzer implements IRetryAnalyzer {
        private static int MAX_RETRY_COUNT = 3;
    
        AtomicInteger count = new AtomicInteger(MAX_RETRY_COUNT);
    
        public boolean isRetryAvailable() {
            return (count.intValue() > 0);
        }
    
        @Override
        public boolean retry(ITestResult result) {
            boolean retry = false;
            if (isRetryAvailable()) {
                System.out.println("Going to retry test case: " + result.getMethod() + ", " + (MAX_RETRY_COUNT - count.intValue() + 1) + " out of " + MAX_RETRY_COUNT);
                retry = true;
                count.decrementAndGet();
            }
            return retry;
        }
    }
    

    POM.xml -> Surefire Configuration:

    This is where you should configure "overwrite" surefire listener wich has their own counters.

    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.18.1</version>
      <configuration>
        <suiteXmlFiles><suiteXmlFile>${basedir}/testng.xml</suiteXmlFile></suiteXmlFiles>
     <properties> 
       <property>
        <name>listener</name>
        <value>Utils.MyTestListenerAdapter,Utils.MyRetryAnalizer</value>
       </property>
     </properties>
    

    0 讨论(0)
  • 2021-01-13 07:24

    You can also try below code

       @AfterTest
    public void afterTest(ITestContext context) {
           Set<ITestResult> passedTests = context.getPassedTests().getAllResults();
           passedTests.forEach(i -> {
               if (context.getFailedTests().getAllMethods().contains(i.getMethod())) {
                   context.getFailedTests().getAllMethods().remove(i.getMethod());
               }
            });
    }
    
    0 讨论(0)
  • 2021-01-13 07:34

    I ended up going for a solution that uses a suite listener.

    The solution may not fully clean up what TestNG / your tests log to the console, but if you use the TestNG Jenkins plugin, and had a failure first on every test, and then success, the test run ends up to be green, which I guess is the most important thing.

    And yes, we run mvn integration-test (not mvn verify) and let the TestNG plugin deal with pass / fail. The solution is quite similar / builds on what was posted here.

    import java.util.Map;
    
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.testng.ISuite;
    import org.testng.ISuiteListener;
    import org.testng.ISuiteResult;
    import org.testng.ITestContext;
    
    /**
     * {@link ISuiteListener} implementation to clean up duplicate test results caused by retrying tests using the
     * {@link RetryAnalyzer}
     */
    public class SuiteResultListener implements ISuiteListener {
    
        private static final Logger LOG = LogManager.getLogger();
    
        @Override
        public void onStart(ISuite suite) {
        }
    
        @Override
        public void onFinish(ISuite suite) {
            LOG.info("Cleaning up duplicate test failures in suite '" + suite.getName() + "' ...");
            final Map<String, ISuiteResult> results = suite.getResults();
            int removedFailures = 0;
            for (ISuiteResult result : results.values()) {
                final ITestContext testContext = result.getTestContext();
    
                removedFailures += TestListenerUtil.cleanUpDuplicateFailures(testContext);
            }
    
            LOG.info("Removed " + removedFailures + " duplicate test failure(s) from suite '" + suite.getName() + "'");
        }
    }
    

    And here's the magic that happens in the TestListenerUtil class:

    public static int cleanUpDuplicateFailures(ITestContext testContext) {
        final String testContextName = testContext.getName();
        int removedFailures = 0;
    
        LOG.info("Cleaning up failures in test context '" + testContextName + "' ...");
        final Set<ITestResult> failedTests = testContext.getFailedTests().getAllResults();
        if (failedTests.isEmpty()) {
            LOG.info("There are no failures in test context '" + testContextName + "'\n");
        } else {
            // collect all id's from passed test
            final Set<Integer> passedTestIds = new HashSet<>();
            final Set<ITestResult> passedTests = testContext.getPassedTests().getAllResults();
            LOG.info("Analyzing " + passedTests.size() + " passed test(s)");
            for (ITestResult result : passedTests) {
                final int testId = TestListenerUtil.getId(result);
                passedTestIds.add(testId);
                LOG.info("  Passed test " + TestListenerUtil.getName(result) + ": #" + testId + " @ "
                        + getStartTime(result));
            }
    
            // check which failed test results should be removed
            final List<Integer> resultsToBeRemoved = new ArrayList<>();
            final Set<Integer> failedTestIds = new HashSet<>();
    
            LOG.info("Analyzing " + failedTests.size() + " failed test(s)");
            for (ITestResult result : failedTests) {
                final int testId = TestListenerUtil.getId(result);
                final String name = TestListenerUtil.getName(result);
    
                // if we saw this test pass or fail before we mark the result for deletion
                if (failedTestIds.contains(testId) || passedTestIds.contains(testId)) {
                    LOG.info("  Adding test " + name + " to be removed: #" + testId + " @ " + getStartTime(result));
                    resultsToBeRemoved.add(testId);
                } else {
                    LOG.info("  Remembering failed test " + name + ": #" + testId + " @ " + getStartTime(result));
                    failedTestIds.add(testId);
                }
            }
    
            // finally delete all duplicate failures (if any)
            final int duplicateFailures = resultsToBeRemoved.size();
            if (duplicateFailures > 0) {
                LOG.info("Cleaning up failed tests (expecting to remove " + resultsToBeRemoved.size()
                        + " result(s)) ...");
                for (ITestResult result : testContext.getFailedTests().getAllResults()) {
                    final int testId = TestListenerUtil.getId(result);
                    final String info = TestListenerUtil.getName(result) + ": #" + testId + " @ "
                            + getStartTime(result);
                    if (resultsToBeRemoved.contains(testId)) {
                        LOG.info("  Removing failed test result " + info);
                        testContext.getFailedTests().removeResult(result);
                        resultsToBeRemoved.remove((Integer) testId);
                        removedFailures++;
                    } else {
                        LOG.info("  Not removing failed test result " + info);
                    }
                }
            }
    
            if (removedFailures == duplicateFailures) {
                LOG.info("Removed " + removedFailures + " failed test result(s) in '" + testContextName + "'\n");
            } else {
                LOG.warn("Removed " + removedFailures + " failed test result(s) in '" + testContextName
                        + "' (expected to remove " + duplicateFailures + ")\n");
            }
        }
    
        return removedFailures;
    }
    

    With those two additional utils methods:

    public static String getName(ITestResult result) {
        final List<String> parameters = new ArrayList<>();
        if (result.getParameters() != null) {
            for (Object parameter : result.getParameters()) {
                if (parameter instanceof TestResult && ((TestResult) parameter).getStatus() < 0) {
                    // TestResult.toString() will explode with status < 0, can't use the toString() method
                    parameters.add(parameter.getClass().getName() + "@" + parameter.hashCode());
                } else {
                    parameters.add(parameter == null ? "null" : parameter.toString());
                }
            }
        }
    
        return result.getTestClass().getRealClass().getSimpleName() + "." + result.getMethod().getMethodName() + "("
                + StringUtils.join(parameters, ",") + ")";
    }
    
    public static int getId(ITestResult result) {
        final HashCodeBuilder builder = new HashCodeBuilder();
        builder.append(result.getTestClass().getRealClass());
        builder.append(result.getMethod().getMethodName());
        builder.append(result.getParameters());
        return builder.toHashCode();
    }
    

    And also, if you're interested how our RetryAnalyzer works, see below.

    One thing that is important to understand, is that we're taking the the parameters of the test method into account in both the RetryAnalyzer and the duplicate result clean up. Those are relevant cause we often work with DataProviders.

    import java.util.HashMap;
    import java.util.Map;
    
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.testng.IRetryAnalyzer;
    import org.testng.ITestResult;
    
    public class RetryAnalyzer implements IRetryAnalyzer {
    
        private static final Logger LOG = LogManager.getLogger();
    
        private static Integer maxRetries;
        private final Map<Integer, Integer> retryCount = new HashMap<>();
    
        @Override
        public boolean retry(ITestResult result) {
            // only re-try failures
            if (result.getStatus() == ITestResult.FAILURE) {
                final String testName = TestListenerUtil.getName(result);
                final int count = getRetryCount(result);
                final int maxRetriesAllowed = getMaxRetriesAllowed();
                if (count < maxRetriesAllowed) {
                    retryCount.put(TestListenerUtil.getId(result), count + 1);
                    LOG.info("Retrying test (attempt " + (count + 1) + "/" + maxRetriesAllowed + "): " + testName);
                    return true;
                } else {
                    LOG.error("Failing test after " + count + " retries: " + testName);
                }
            }
    
            return false;
        }
    
        public boolean canRetry(ITestResult result) {
            return result.getStatus() == ITestResult.FAILURE && getRetryCount(result) < getMaxRetriesAllowed();
        }
    
        private int getRetryCount(ITestResult result) {
            final int testId = TestListenerUtil.getId(result);
            return retryCount.containsKey(testId) ? retryCount.get(testId) : 0;
        }
    
        public static int getMaxRetriesAllowed() {
            return maxRetries == null ? Config.MAX_TEST_RETRIES : maxRetries;
        }
    }
    
    0 讨论(0)
提交回复
热议问题