如何解决TestNG Retry的问题

我们使用TestNG来作为自动化测试框架的基础。在运行一批test case之后呢,我们希望能够自动Retry failed的test case。TestNG提供了一个IRetryAnalyzer 的接口,在@Test annotation里面可以指定使用Retry类来执行retry的功能。
@Test(retryAnalyzer = TestRetryAnalyzer.class)

TestRetryAnalyzer类实现了IRetryAnalyzer接口。
实现逻辑为:在retry方法里,判断Retry次数是否已经超过指定的最大retry次数。如果没有返回true,否则返回false。
_______________________________________________________________________________
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;

public class TestRetryAnalyzer implements IRetryAnalyzer {
private static final String TEST_RETRY_COUNT = "testRetryCount";
private int count = 1;
private int maxCount = 1;

public TestRetryAnalyzer() {
String retryMaxCount = System.getProperty(TEST_RETRY_COUNT);
if (retryMaxCount != null) {
maxCount = Integer.parseInt(retryMaxCount);
}
}

public int getCount() {
return this.count;
}

public int getMaxCount() {
return this.maxCount;
}

public synchronized boolean retry(ITestResult result) {
String testClassName = String.format("%s.%s", result.getMethod()
.getRealClass().toString(), result.getMethod().getMethodName());

if (count <= maxCount) {
result.setAttribute("RETRY", new Integer(count));

Logging.log("[RETRYING] " + testClassName + " FAILED, "
+ "Retrying " + count + " time", true);

count += 1;
return true;
}
return false;
}
}
_______________________________________________________________________________

然后,我们需要定义一个TestNGListener,来处理TestCase Retry的状态。在onTestFailure方法里,未达到最大retry次数的失败Case,我们把它的状态设置为SKIP,这样TestNG不会把它统计为Failed的test case。

在OnFinish方法里面处理最后TestCase的Result。

public class RetryTestListener extends TestListenerAdapter  {

public synchronized void onTestFailure(ITestResult arg0) {
if (arg0.getMethod().getRetryAnalyzer() != null) {
TestRetryAnalyzer testRetryAnalyzer = (TestRetryAnalyzer) arg0.getMethod().getRetryAnalyzer();

if (testRetryAnalyzer.getCount() <= testRetryAnalyzer.getMaxCount()) {
arg0.setStatus(ITestResult.SKIP);
Reporter.setCurrentTestResult(null);
}
else
failedCases.addResult(arg0, arg0.getMethod());

isRetryHandleNeeded = true;
}
}

public void onFinish(final ITestContext arg0) {
if(isRetryHandleNeeded)
{
removeIncorrectlySkippedTests(arg0,failedCases);
removeFailedTestsInTestNG(arg0);
}else
{
skippedCases = arg0.getSkippedTests();
failedCases = arg0.getFailedTests();
}

}

TestNG可能会把TestCase同时放在Skipped和Failed里面,所以我们在Skipped的testcase里面剔除真正Failed的Test Case(最后一次Retry失败的Case)

protected IResultMap removeIncorrectlySkippedTests(ITestContext test,IResultMap map)
{   
  List<ITestNGMethod> failsToRemove = new ArrayList<ITestNGMethod>();
  IResultMap returnValue = test.getSkippedTests();

  for(ITestResult result : returnValue.getAllResults())
  {
    for(ITestResult resultToCheck : map.getAllResults())
    {
        if(resultToCheck.getMethod().equals(result.getMethod()))
        {
            failsToRemove.add(resultToCheck.getMethod());
            break;
        }
    }  
    for(ITestResult resultToCheck : test.getPassedTests().getAllResults())
    {
        if(resultToCheck.getMethod().equals(result.getMethod()))
        {
            failsToRemove.add(resultToCheck.getMethod());
            break;
        }
    }   
  }

  for(ITestNGMethod method : failsToRemove)
  {
      returnValue.removeResult(method);
  }
  skippedCases = returnValue;


  return returnValue;
}

IResultMap只提供了删除某个方法的removeResult(Method)方法,如果使用了Dataprovider,我们不能直接调用此方法在FailedResult里面直接删除该方法的结果。这会造成test case结果的混淆。同一个TestMethod可能一个Data的结果Pass,另一个Data的结果Failed。所以我们使用failedCases来记录真正失败的result。同时我们在自己的Report中把FailedCases显示出来。
在下面这个方法里面,我们在failedTests里面查找,如果failedCases里面没有该方法,就把该方法的结果删除掉。这样确保TestNG能够报告Suite的整体Pass/Fail状态。有些同学问,为什么要这么做?我们之前不是把testcase设置为skip了么?还记得我们前面提过的,TestNG可能会把result放在两个Map里面了,所以还是需要做这一步来确保正确。

private void removeFailedTestsInTestNG(ITestContext test)
{   
  IResultMap returnValue = test.getFailedTests();


  for(ITestResult result : returnValue.getAllResults())
  {

    boolean isFailed = false;
  
    for(ITestResult resultToCheck : failedCases.getAllResults())
    {
        if(result.getMethod().equals(resultToCheck.getMethod()))
        {
        System.out.println("Passed:"+result.getMethod().getMethodName());
        isFailed = true;
            break;
        }
    }
    if(!isFailed)
    {
    returnValue.removeResult(result.getMethod());
    test.getFailedConfigurations().removeResult(result.getMethod());
    }
  }

}

}

我们这么做了之后,基本能够保证报告的正确性。但是TestNG的Retry对DataProvider的支持 不 够 好,详 情 参 考https://groups.google.com/forum/#!msg/testng-users/7S9BFshqivk/R0dCG14kSccJ 
另外,此代码基于TestNG 6.7 之上。

CI 中也需要做修改。否则Retry成功了,CI仍然会报告失败。

   
<plugin>                                      <groupId>org.apache.maven.plugins</groupId>                                      <artifactId>maven-surefire-plugin</artifactId>                                      <version>2.9</version>                                      <configuration>                                                <suiteXmlFiles>                                                         <suiteXmlFile>src/main/java/com/ebay/maui/test/demo/testng_retry.xml</suiteXmlFile>                                                </suiteXmlFiles>                                                <properties>                                                         <property>                                                           <name>usedefaultlisteners</name>                                                           <value>false</value>                                                         </property>                                                         <property>                                                           <name>listener</name>                                                           <value>com.ebay.maui.reporter.HTMLReporter</value>                                                         </property>                                                </properties>                                      </configuration>                            
</plugin>

猜你喜欢

转载自shijunjuan.iteye.com/blog/1678735