测试用例运行稳定性是自动化质量的一个重要指标,在运行中需要尽可能的剔除非bug造成的测试用例执行失败,对于失败用例进行重跑是常用策略之一。一种重跑策略是所有用例运行结束后对失败用例重跑,另一种重跑策略是在运行时监控用例运行状态,失败后实时重跑。
下面,详细介绍TestNG 如何对失败测试用例实时重跑并解决重跑过程中所遇到问题的实践和解决方案。对失败测试用例进行实时重跑,有以下几个方面需求:
对于希望测试用例中的少量易失败,不稳定的测试用例进行重跑,可采用这种方式。
以下是TestNG处理测试用例运行结果的部分代码。
IRetryAnalyzer retryAnalyzer = testMethod.getRetryAnalyzer();
boolean willRetry = retryAnalyzer != null && status == ITestResult.FAILURE && failure.instances != null && retryAnalyzer.retry(testResult);
if (willRetry) {
resultsToRetry.add(testResult);
failure.count++;
failure.instances.add(testResult.getInstance());
testResult.setStatus(ITestResult.SKIP);
} else {
testResult.setStatus(status);
if (status == ITestResult.FAILURE && !handled) {
handleException(ite, testMethod, testResult, failure.count++);
}
分析以上代码,其中,接口IretryAnalyzer的方法retry()的返回值作为是否对失败测试用例进行重跑的一个条件。如果retry()结果为true,则该失败测试用例会重跑,同时将本次失败结果修改为Skip;如果结果为false,则失败的测试用例保持失败结果,运行结束。因此,如果你希望失败测试用例重跑的话,需要把IretryAnalyzer的retry()方法重写,插入自己定义的逻辑,设置返回值为true。
创建类RetryImpl,重写retry()方法,设置失败测试用例的重跑次数,代码如下:
public class RetryImpl implements IRetryAnalyzer {
private int count = 1;
private int max_count = 3; // Failed test cases could be run 3 times at most
@Override
public boolean retry(ITestResult result) {
System.out.println("Test case :"+result.getName()+",retry time: "+count+"");
if (count < max_count) {
count++;
return true;
}
return false;
}
}
public class TestNGReRunDemo {
@Test(retryAnalyzer=RetryImpl.class)
public void test01(){
Assert.assertEquals("success","fail");
System.out.println("test01");
}
}
以上测试用例test01可重复运行3次。
如果希望所有失败的测试用例都进行重跑,采用retryAnalyzer注解方式对每个测试用例进行注解就比较麻烦。通过实现IAnnotationTransformer接口的方式,可以对全量测试用例的重试类进行设置。该接口是一个监听器接口,用来修改TestNG注解。IAnnotationTransformer监听器接口只有一个方法:transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod). 上文中,我们自定义了类RetryImpl 实现接口IRetryAnalyzer。TestNG通过transfrom()方法修改retryAnalyzer注解。以下代码对retryAnalyzer注解进行修改设置。
创建类RetryListener,代码如下。
public class RetryListener implements IAnnotationTransformer {
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
IRetryAnalyzer retry = annotation.getRetryAnalyzer();
if (retry == null) {
annotation.setRetryAnalyzer(RetryImpl.class);
}
}
}
TestNG可以在配置文件或者测试类中对Listener
类进行配置。
@Listeners({RetryListener.class})
public class TestNGReRunDemo {
@Test
public void test01(){
Assert.assertEquals("success","fail");
System.out.println("test01");
}
}
配置完成后,运行测试用例test01,运行结果显示test01将重跑次数3次。
进一步分析TestNG的运行代码,其在对失败运行用例重跑时,逻辑如下图。
对于通过dependsOnMethods 或dependsOnGroups注解依赖于其他测试用例的测试用例来讲 ,测试用例执行分为两种情况:
被依赖的测试用例失败后进行了重跑,并重跑成功。(注:在RetryImpl类中,已设置最大重跑次数max_count = 3)
public static int number =0;
@Test
public void test01(){
number++;
System.out.println(String.valueOf(number));
Assert.assertEquals(number,2);
System.out.println("test01");
}
@Test(dependsOnMethods = "test01") // alwaysRun = false by default
public void test02(){
System.out.println("test02 is running only if test01 is passed.");
}
1、TestNG测试报告
2、问题
测试用例 | 运行次数 | 运行情况 | 测试报告 |
---|---|---|---|
Test01 | 2 | 第一次:skipped ; 第二次:passed | 在Skipped 和Passed的统计数量中,test01被分别记录一次 |
Test02 | 0 | Skipped | 记录一次Skipped |
被依赖的测试用例失败后进行了重跑,并且重跑没有成功。(注:在RetryImpl类中,已设置最大重跑次数max_count = 3)
public static int number =0;
@Test
public void test01(){
number++;
System.out.println(String.valueOf(number));
Assert.assertEquals(number,10);
System.out.println("test01");
}
@Test(dependsOnMethods = "test01") // alwaysRun = false by default
public void test02(){
System.out.println("test02 is running only if test01 is passed.");
}
1、TestNG测试报告
2、问题
测试用例 | 运行次数 | 运行结果 | 测试报告 |
---|---|---|---|
Test01 | 3 | 第一次:skipped;第二次:skipped;第三次:failed | 在Skipped统计数量中,test01被被记录两次在failed统计中,test01被记录一次 |
Test02 | 0 | Skipped | 记录一次Skipped |
以下方案解决重跑测试用例成功后后继测试用例无法继续运行的问题,并对测试报告进行优化。
根据上面分析的TestNG逻辑,在对依赖测试用例的结果进行检查如果忽略重跑的中间结果只检查最后一次的运行结果,可以达到需求的。对于测试报告,同样的处理方式,忽略所有中间的测试用例运行结果,只记录最后结测试用例的中间运行结果为Skipped,下面的代码通过重写TestListenerAdapter的onTestSuccess()和onTestFailure()方法,对测试用例的中间结果skipped进行了。代码如下:
public class ResultListener extends TestListenerAdapter {
@Override
public void onTestFailure(ITestResult tr) {
if(tr.getMethod().getCurrentInvocationCount()==1)
{
super.onTestFailure(tr);
return;
}
processSkipResult(tr);
super.onTestFailure(tr);
}
@Override
public void onTestSuccess(ITestResult tr) {
if(tr.getMethod().getCurrentInvocationCount()==1)
{
super.onTestSuccess(tr);
return;
}
processSkipResult(tr);
super.onTestSuccess(tr);
}
// Remove all the dup Skipped results
public void processSkipResult(ITestResult tr)
{
ITestContext iTestContext = tr.getTestContext();
Iterator<ITestResult> processResults = iTestContext.getSkippedTests().getAllResults().iterator();
while (processResults.hasNext()) {
ITestResult skippedTest = (ITestResult) processResults.next();
if (skippedTest.getMethod().getMethodName().equalsIgnoreCase(tr.getMethod().getMethodName()) ) {
processResults.remove();
}
}
}
}
在配置文件进行全局设置或者在测试类中标记。
1、 结果验证
2、结果分析:
测试用例 | 运行次数 | 运行结果 | 测试报告 |
---|---|---|---|
Test01 | 2 | 第一次:skipped;第二次:passed | 只在Passed的统计数量中test01被记录一次 |
Test02 | 1 | Passed | 记录一次passed |
1、结果验证
2、结果分析:
测试用例 | 运行次数 | 运行结果 | 测试报告 |
---|---|---|---|
Test01 | 3 | 第一次:skipped;第二次:skipped;第三次:failed | test01只在failed统计中被记录一次 |
Test02 | 1 | Skipped | 依赖用例执行失败,test02结果为Skipped,只记录一次结果Skipped |
领取专属 10元无门槛券
私享最新 技术干货