TestNG测试用例重跑详解及实践优化
测试用例运行稳定性是自动化质量的一个重要指标,在运行中需要尽可能的剔除非bug造成的测试用例执行失败,对于失败用例进行重跑是常用策略之一。一种重跑策略是所有用例运行结束后对失败用例重跑,另一种重跑策略是在运行时监控用例运行状态,失败后实时重跑。
下面,详细介绍TestNG如何对失败测试用例实时重跑并解决重跑过程中所遇到问题的实践和解决方案。对失败测试用例进行实时重跑,有以下几个方面需求:
- 测试用例运行失败,监听到失败后立即进行重跑
- 测试用例通过
dependsOnMethods/dependsOnGroups标记依赖其他测试用例,在被依赖的测试用例重跑运行成功后,该测试用例可以继续运行 - 对于重跑多次的测试用例,只记录最后一次运行成功或失败结果
第一部分 测试用例重跑
1.1 retryAnalyzer注解方式
对于希望测试用例中的少量易失败,不稳定的测试用例进行重跑,可采用这种方式。
1.1.1 原理
以下是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。
1.1.2 代码
创建类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;
}
}
1.1.3 实例
public class TestNGReRunDemo {
@Test(retryAnalyzer=RetryImpl.class)
public void test01(){
Assert.assertEquals("success","fail");
System.out.println("test01");
}
}
以上测试用例test01可重复运行3次。
1.2 实现接口IAnnotationTransformer方法
如果希望所有失败的测试用例都进行重跑,采用retryAnalyzer注解方式对每个测试用例进行注解就比较麻烦。通过实现IAnnotationTransformer接口的方式,可以对全量测试用例的重试类进行设置。
该接口是一个监听器接口,用来修改TestNG注解。IAnnotationTransformer监听器接口只有一个方法:transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod). 上文中,我们自定义了类RetryImpl 实现接口IRetryAnalyzer。TestNG通过transfrom()方法修改retryAnalyzer注解。以下代码对retryAnalyzer注解进行修改设置。
1.2.1代码
创建类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);
}
}
}
1.2.2 配置Listener
TestNG可以在配置文件或者测试类中对Listener类进行配置。
- 方法一:在TestNG的配置XML中进行以下配置
<listeners>
<listener class-name="PackageName.RetryListener"></listener>
</listeners>
- 方法二:在测试类中通过@Listeners配置
@Listeners({RetryListener.class})
public class TestNGReRunDemo {
@Test
public void test01(){
Assert.assertEquals("success","fail");
System.out.println("test01");
}
}
配置完成后,运行测试用例test01,运行结果显示test01将重跑次数3次。
第二部分 被依赖的测试用例重跑结果处理
进一步分析TestNG的运行代码,其在对失败运行用例重跑时,逻辑如下图。

对于通过dependsOnMethods 或dependsOnGroups注解依赖于其他测试用例的测试用例来讲,测试用例执行分为两种情况:
- alwaysRun=true,则无论所依赖的测试用例执行情况如何,该测试用例都会执行,即所依赖的测试用例重跑不会影响该测试用例的执行。
- alwaysRun=false,或者保持缺省值(false),依赖于其他测试用例或测试用例组的测试结果,在运行时TestNG获取所依赖的测试用例的运行结果,检查依赖的测试用例是否全部执行成功,如果不全部成功,则把该测试用例结果设置为Skipped。
2.1 场景分析:场景一
被依赖的测试用例失败后进行了重跑,并重跑成功。(注:在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 |
测试报告:test01运行结果全部被记录,而用例重跑,只希望记录最后的结果。
运行情况:测试用例test02依赖于测试用例test01运行结果,在test01重跑成功后,测试用例test02没有执行,不符合需求预期。
2.2 场景分析:场景二
被依赖的测试用例失败后进行了重跑,并且重跑没有成功。(注:在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 |
- 运行情况:测试用例test02依赖于测试用例test01运行结果,在test01重跑失败后,测试用例test02没有执行,这种情况符合需求预期。
- 测试报告:同场景一,test01重跑失败,运行结果全部被记录,而用例重跑,只希望记录最后的结果。
第三部分 优化解决方案
以下方案解决重跑测试用例成功后后继测试用例无法继续运行的问题,并对测试报告进行优化。
3.1 TestListenerAdapter方法重写
根据上面分析的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();
}
}
}
}
3.2 配置结果处理Listener类
在配置文件进行全局设置或者在测试类中标记。
- 方法一:在TestNG的配置XML中进行以下配置
<listeners>
<listener class-name="PackageName.ResultListener"></listener>
</listeners>
- 方法二:在测试类中通过@Listeners配置
@Listeners({ResultListener.class})
public class TestNGReRunDemo {
@Test
public void test01(){
Assert.assertEquals("success","fail");
System.out.println("test01");
}
}
3.3 场景一
1、 结果验证

2、结果分析:
| 测试用例 | 运行次数 | 运行结果 | 测试报告 |
|---|---|---|---|
| Test01 | 2 | 第一次:skipped;第二次:passed | 只在Passed的统计数量中test01被记录一次 |
| Test02 | 1 | Passed | 记录一次passed |
3.4 场景二
1、结果验证

2、结果分析:
| 测试用例 | 运行次数 | 运行结果 | 测试报告 |
|---|---|---|---|
| Test01 | 3 | 第一次:skipped;第二次:skipped;第三次:failed | test01只在failed统计中被记录一次 |
| Test02 | 1 | Skipped | 依赖用例执行失败,test02结果为Skipped,只记录一次结果Skipped |
作者:耿燕飞
TestNG测试用例重跑详解及实践优化的更多相关文章
- Java自动化测试框架-12 - TestNG之xml文件详解篇 (详细教程)
1.简介 现在这篇,我们来学习TestNG.xml文件,前面我们已经知道,TestNG就是运行这个文件来执行测试用例的.通过本篇,你可以进一步了解到:这个文件是配置测试用例,测试套件.简单来说,利用这 ...
- 《手把手教你》系列基础篇(八十三)-java+ selenium自动化测试-框架设计基础-TestNG测试报告-下篇(详解教程)
1.简介 其实前边好像简单的提到过测试报告,宏哥觉得这部分比较重要,就着重讲解和介绍一下.报告是任何测试执行中最重要的部分,因为它可以帮助用户了解测试执行的结果.失败点和失败原因.另一方面,日志记录对 ...
- 小甲鱼PE详解之基址重定位详解(PE详解10)
今天有一个朋友发短消息问我说“老师,为什么PE的格式要讲的这么这么细,这可不是一般的系哦”.其实之所以将PE结构放在解密系列继基础篇之后讲并且尽可能细致的讲,不是因为小甲鱼没事找事做,主要原因是因为P ...
- TestNg失败重跑—解决使用 dataProvider 参数化用例次数冲突问题
问题背景 在使用 testng 执行 UI 自动化用例时,由于 UI自动化的不稳定性,我们在测试的时候,往往会加上失败重跑机制.在不使用 @DataProvider 提供用例参数化时,是不会有什么问题 ...
- 《手把手教你》系列基础篇(八十四)-java+ selenium自动化测试-框架设计基础-TestNG日志-上篇(详解教程)
1.简介 TestNG还为我们提供了测试的记录功能-日志.例如,在运行测试用例期间,用户希望在控制台中记录一些信息.信息可以是任何细节取决于目的.牢记我们正在使用Selenium进行测试,我们需要有助 ...
- SVM-支持向量机原理详解与实践
前言 去年由于工作项目的需要实际运用到了SVM和ANN算法,也就是支持向量机和人工神经网络算法,主要是实现项目中的实时采集图片(工业高速摄像头采集)的图像识别的这一部分功能,虽然几经波折,但是还好最终 ...
- testng失败重跑
重跑失败场景 1.要添加两个文件 背景:因为这里只是想单独展示失败的重跑的案例,所以先暂时把app这块的运行注释掉,只跑一个简单的demo,就一个简单类,类中就3个测试方法,失败重跑的原理是,运行方法 ...
- .NET ORM框架 SqlSuagr4.0 功能详解与实践【开源】
SqlSugar 4.0 ORM框架的优势 为了未来能够更好的支持多库分布式的存储,并行计算等功能,将SqlSugar3.x全部重写,现有的架构可以轻松扩展多库. 源码下载: https://gith ...
- [动图演示]Redis 持久化 RDB/AOF 详解与实践
Redis 是一个开源( BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.它支持的数据类型很丰富,如字符串.链表.集 合.以及散列等,并且还支持多种排序功能. 什么叫持 ...
随机推荐
- spark rdd元素println
1.spark api主要分两种:转换操作和行动操作.如果在转化操作中println spark打印了 我也看不到. val result = sqlContext.sql(sql) val resu ...
- Django-rest-framework 是个什么鬼?
作者:HelloGitHub-追梦人物 我们首先来回顾一下传统的基于模板引擎的 django 开发工作流: 绑定 URL 和视图函数.当用户访问某个 URL 时,调用绑定的视图函数进行处理. 编写视图 ...
- win10+ubuntu双系统修复ubuntu启动引导
因为windows是不能引导linux的,而每次win10升级或恢复都会将linux的启动引导覆盖掉,导致无法进入linux, 所以一直就禁止了win10更新.这几天win10出了点小毛病,所以就狠下 ...
- 搞搞hibernate.current_session_context_class
搞搞hibernate.current_session_context_class 分类: 排错记录2010-09-01 18:14 4155人阅读 评论(3) 收藏 举报 sessionhibern ...
- webWMS开发过程记录(三)- 需求分析(略)
行业:汽车零部件制造 大方向:非唯一码,需有一套简单.易用.受控的误操作撤回机制 现状(略) 目标(略) 注:由于项目是自己根据以往经验,自己开发的,且开发时间不固定,故需求分析暂略,我会把工作重点放 ...
- 植物大战僵尸的代码如何使用python来实现
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:程序IT圈 PS:如有需要Python学习资料的小伙伴可以加点击下方链 ...
- c++全排列
一.概念 从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列.当m=n时所有的排列情况叫全排列.如果这组数有n个,那么全排列数为n!个. 比如a ...
- 带权值的图 BFS
用bfs遍历最图求最短路径时通常借用优先队列即优先考虑最大的或者最小的权值 方法1 优先队列:(内置函数,优先考虑较小的权值) #include<iostream> #include< ...
- Linux学习笔记(一)目录处理命令
目录处理命令 ls cd mkdir rmdir tree ls 英文原意: list 功能: 显示目录文件 语法: ls 选项[-ald] [文件或目录] ls -a 显示所有文件,包括隐藏文件 l ...
- P1464 Function
Function 简 单 的 递 归 这道题一开始十分智障地用递归做,虽然知道没那么简单,但还是冒着送死的心态交了一遍,果然,如我所料 样例输入: 密密麻麻,几万行的样例输入 //:) ...