2015-03-17 10 views
5

Ähnlich wie die hier veröffentlichte Lösung TestNG retrying failed tests doesn't output the correct test results, ich versuche, ein (doppeltes) Testergebnis mit einem Test-Listener während onFinish (ITestContext Kontext) zu entfernen.Entfernen (Duplizieren) fehlgeschlagen TestNG Ergebnis über Test-Listener

Obwohl das Entfernen des Ergebnisses mit context.getFailedTests(). RemoveResult (result) ordnungsgemäß zu funktionieren scheint (das Ergebnis wird tatsächlich entfernt), scheint "an anderer Stelle" die Ursache zu liegen Der Build schlägt immer noch fehl.

Beachten Sie auch, dass, wenn ich die Probe-Test aus dem Artikel oben laufen (was ein doppelter Ausfall muss entfernt werden und eine bestandene Prüfung), habe ich einen Unterschied in der „Testergebnisse“ erhalten vs. (nicht wie erwartet bereinigt) "Suite-Ergebnisse" (doppelte Fehler wurden wie erwartet entfernt).

Und, wo zieht die Berichterstattung die Ergebnisse aus, um zu entscheiden, ob der Build fehlschlägt? Oder ist es nur, dass es die Ergebnisse zieht, bevor ich die fehlgeschlagenen Tests bereinige ...?

=============================================== 
    Default test 
    Tests run: 3, Failures: 2, Skips: 0 
=============================================== 

=============================================== 
Default suite 
Total tests run: 2, Failures: 1, Skips: 0 
=============================================== 

EDIT: Nur um zu klären, werden wir diese Tests mit Maven ausgeführt wird, und sie sind die, so laufen wir sie mit dem Failsafe-Plugin. Das Problem ist, dass, obwohl die Tests anscheinend entfernt werden, mvn verify immer noch den Build durchlässt, da Build-Fehler unabhängig davon gefunden werden.

Und auch wenn Sie einen solchen Test von Eclipse ausführen, obwohl die Tests entfernt wurden, werden weiterhin Fehler im Protokoll gedruckt, wenn die Suite beendet ist.

Über RetryAnalyzer: Ich würde RetryAnalyzer überhaupt nicht als gute/bewährte Methode betrachten, aber wenn Sie sich in einer Situation befinden, in der Sie das Problem lösen müssen, z. Wenn Sie eine Testsuite geerbt haben, die auf RetryAnalyzer basiert, können Sie dies als nützlich empfinden.

+0

Haben Sie zwischendurch eine Lösung für dieses Problem gefunden? – MrSpock

+0

Wir haben irgendwie etwas "Nutzbares" herausgefunden, obwohl nicht das, was ich letztendlich wollte. Ich werde versuchen, in den nächsten Tagen etwas Zeit zu finden, um den Code aufzuräumen und zu posten. – mac

+0

Begierig, deine Lösung zu sehen! – MrSpock

Antwort

2

Versuchen Sie diesen Code verwenden:

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 -> todsichere Konfiguration:

Hier sollten Sie den todsicheren Listener konfigurieren, der über eigene Zähler verfügt.

<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

Danke, aber ich fürchte, das ist nicht zuverlässig zur Arbeit gehen. Zunächst betrachten wir Testläufe mit Skips als nicht erfolgreich. Zweitens scheint die Lösung ein wenig riskant zu sein, da nicht jeder RetryAnalyzer von diesem Typ sein könnte (vielleicht in kleineren Projekten, aber nicht in größeren Projekten, die mehrere Maven-Projekte umfassen). Die Idee mit dem Reporter.setCurrentStatus() scheint jedoch interessant. Außerdem, eine Randnotiz: Diese Art von RetryAnalyzer-Lösung funktioniert nicht ordnungsgemäß für Testmethoden mit Parametern/Datenprovidern. – mac

+0

@mac Aktualisiert: Fügen Sie Code „OnFinish“ die Art und Weise Sie übersprungenen müssen zu handhaben und nicht bestandenen Tests – Morvader

+0

im Allgemeinen, ich glaube, Sie haben jetzt einige Widersprüche in Ihrem Code, wie Sie den Test als Sprung sind markiert, aber dann Sie‘ um Fehler später zu beheben. Ich denke auch, dass Sie den ganzen Teil, den Sie in onTestFailure() haben, einfach in den RetryAnalyzer selbst verschieben können, und Sie würden dann auch nicht das riskante Casting durchführen müssen. Im Allgemeinen denke ich, dass das immer noch derselbe Ansatz ist wie in dem Link, den ich oben anbrachte. Der Punkt ist aber, dass, wenn Sie all das tun, ich glaube, die MVN Phase überprüfen (Ursache wir tun dies tatsächlich für seine mit ausfallsicheren nicht todsichere) scheitern wird noch die Build – mac

0

I endete für eine Lösung, gehen, die eine Suite Listener verwendet.

Die Lösung nicht vollständig aufzuräumen kann, was TestNG/Ihre Tests an die Konsole anmelden, aber wenn Sie die TestNG Jenkins-Plugin verwenden, und einen Fehler hatten zunächst bei jedem Test, und dann Erfolg, der Testlauf sein endet Grün, was ich für das Wichtigste halte.

Und ja, wir laufen mvn Integrationstest (nicht mvn überprüfen) und lassen Sie das TestNG-Plugin mit 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() + "'"); 
    } 
} 

Und hier ist die Magie, die in der TestListenerUtil Klasse passiert:

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

Mit diesen beiden zusätzlichen utils Methoden:

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

Und auch, wenn Sie daran interessiert sind, wie unsere RetryAnalyzer funktioniert, siehe unten.

Eine Sache, die wichtig zu verstehen ist, ist, dass wir in die die Parameter des Testverfahrens zu berücksichtigen sind dabei sowohl die RetryAnalyzer und dem doppelten Ergebnis aufzuräumen. Diese sind relevant, weil wir oft mit DataProvidern arbeiten.

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

ich auf die Frage bin zurück. Habe gerade gemerkt, dass das Testen das Verhalten in 6.9.5 verändert hat! Tests, die wiederholt werden, werden jetzt auf Übersprungen gesetzt. Siehe https://github.com/cbeust/testng/issues/878. Das würde jeden TestListener, der fehlgeschlagene Tests entfernt, entfernen. – MrSpock

+0

da stimme ich nicht unbedingt zu: Ich denke du willst dann die übersprungenen noch entfernen. Aber gut zu wissen, dass sich das in 6.9.5 ändert! Vielen Dank. – mac

Verwandte Themen