TestNg失败重跑—解决使用 dataProvider 参数化用例次数冲突问题
问题背景
在使用 testng 执行 UI 自动化用例时,由于 UI自动化的不稳定性,我们在测试的时候,往往会加上失败重跑机制。在不使用 @DataProvider 提供用例参数化时,是不会有什么问题,如果使用了的话就会出现多条用例都是失败时,重跑机制只会执行第一次失败的用例,其他用例的失败重跑就不执行了。
如下:提供的两组参数都是失败时!(重跑的次数设置为2次)

从上图中可以看出,第一次失败的用例有重跑了2次,第二次失败的用例就没有重跑2次。
TestNg重跑机制代码实现
TestNg提供的重跑机制,实现思路如下:
- 创建一个实现类,实现
IRetryAnalyzer接口,重写该接口的retry方法,定义失败重跑的规则。 - 创建一个实现类,实现
IAnnotationTransformer接口,重写接口transform方法,用于监听所有的@Test 注解的测试方法。 - 在 TestNg 的 xml 文件中配置监听。
创建 IRetryAnalyzer 实现类
package com.ggf.testng.listener;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
/**
* @Description: 失败重试方法
* 用来监听用例的执行情况,如果断言失败,或者代码出现错误了
* 都会被这个方法进行捕获到,然后通过返回值来判断是否进行重试。
* @Author: ggf
* @Date: 2020/07/20
*/
public class TestngRetry implements IRetryAnalyzer {
/**
* 最大的重跑次数
* 设置用例最多重跑多少次
*/
private int maxRetryCount = 2;
/**
* 当前的重跑的次数
*/
private int currentRetryCount = 1;
/**
* 复写 IRetryAnalyzer 的方法,所有的用例执行完后的结果都会
* 封装到这个对象ITestResult 传入到 retry.xml 方法,通过这个方法
* 返回值来判断是否需要重新执行用例。false :不重跑 true:重跑。
* @param iTestResult
* @return
*/
@Override
public boolean retry(ITestResult iTestResult) {
//如果retry方法返回为true--》执行重试机制
//如果返回是为false --》不会去执行重试
//什么时候会运行到这里??条件-->测试用例执行失败
if(currentRetryCount <= maxRetryCount){
//运行了一次重试机制之后,我们就加1
//如果运行第一次重试机制-->用例执行成功了,用例的结果是pass的
//如果运行第一次重试机制-->用例执行成功了,第二次重试机制不会运行
System.out.println("重跑第【"+currentRetryCount+"】次!");
currentRetryCount++;
return true;
}else{
return false;
}
}
}
创建 IAnnotationTransformer 实现类
package com.ggf.testng.listener;
import org.testng.IAnnotationTransformer;
import org.testng.IRetryAnalyzer;
import org.testng.annotations.ITestAnnotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @Description:
* 由于在使用重跑机制的时候需要在每个用例@Test注解添加 retryAnalyzer 属性。
* 如果用例量过大的话,非常的麻烦,所以我们引入 testng 提供的监听器类:IAnnotationTransformer
* 通过这个监听器类来实现,动态的修改@Test注解属性,我们就可以统一给 @Test 注解动态加上属性retryAnalyzer 值。
* @Author: ggf
* @Date: 2020/07/20
*/
public class RetryListener implements IAnnotationTransformer {
@Override
public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method) {
//1、拿到@test注解的retryAnalyzer属性对象
IRetryAnalyzer iRetryAnalyzer = iTestAnnotation.getRetryAnalyzer();
//2、如果@test的retryAnalyzer属性没有设置,iRetryAnalyzer-->null
if(iRetryAnalyzer == null){
iTestAnnotation.setRetryAnalyzer(TestngRetry.class);
}
}
}
xml 文件配置监听器
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="retry">
<test name="retryTest">
<classes>
<class name="com.ggf.testng.listener.RetryDemo"></class>
</classes>
</test>
<listeners>
<!--失败重试监听器-->
<listener class-name="com.ggf.testng.listener.RetryListener"></listener>
</listeners>
</suite>
测试类
package com.ggf.testng.listener;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
* @Description:
* @Author: ggf
* @Date: 2020/07/20
*/
public class RetryDemo {
@Test(dataProvider = "data")
public void testRetry(String data1, String data2) {
// 断言两个参数是否一样
Assert.assertEquals(data1, data2);
}
@DataProvider
public Object[][] data() {
// 提供两组测试参数
return new Object[][]{{"111","123"}, {"123", "1234"}};
}
}
失败用例重跑问题重现
对于一个使用了dataProvider的用例,因为这个用例是一个标记为@Test的方法,会共用TestngRetry的currentRetryCount,即整个方法的所有参数化用例,总共只会重跑 2 次。例如一个参数化用例有 2 组参数,如果全部正确,每个用例只会输出一次:
Test1: success
Test2: success
如果两组参数的用例都失败了,对于第一组参数是会重跑2次的(代码设置的是2次,不包含第一次),到了第二组参数就不会继续重跑了,因为currentRetryCount在第一组参数用例跑完,当前值就为 3 了,这个时候不满足重跑条件,第二组参数用例失败后就不会重跑了。
Test1: failed -> skipped
Test1: failed -> skipped
Test1: failed
Test2: failed
Test2: failed
至于这里的 Test2 为什么会跑 2 次,没搞明白,正常来说应该是跑一次的,因为程序中 if(currentRetryCount <= maxRetryCount) ` 满足才会重跑,但是这里是 false 所以不会重跑才对。如果有大神们知道为什么,希望不吝赐教,留个言。。。
问题解决方案
要解决上述的问题,需要在每组参数化用例结束(无论成功,失败)后,重置 currentRetryCount 的值,让当前的次数保持在初始化的状态。
在实现类 TestngRetry 中加上一个重置的方法,如下:
package com.ggf.testng.listener;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
/**
* @Description: 失败重试方法
* 用来监听用例的执行情况,如果断言失败,或者代码出现错误了
* 都会被这个方法进行捕获到,然后通过返回值来判断是否进行重试。
* @Author: ggf
* @Date: 2020/07/20
*/
public class TestngRetry implements IRetryAnalyzer {
/**
* 最大的重跑次数
* 设置用例最多重跑多少次
*/
private int maxRetryCount = 2;
/**
* 当前的重跑的次数
*/
private int currentRetryCount = 1;
/**
* 复写 IRetryAnalyzer 的方法,所有的用例执行完后的结果都会
* 封装到这个对象ITestResult 传入到 retry.xml 方法,通过这个方法
* 返回值来判断是否需要重新执行用例。false :不重跑 true:重跑。
* @param iTestResult
* @return
*/
@Override
public boolean retry(ITestResult iTestResult) {
//如果retry方法返回为true--》执行重试机制
//如果返回是为false --》不会去执行重试
//什么时候会运行到这里??条件-->测试用例执行失败
if(currentRetryCount <= maxRetryCount){
//运行了一次重试机制之后,我们就加1
//如果运行第一次重试机制-->用例执行成功了,用例的结果是pass的
//如果运行第一次重试机制-->用例执行成功了,第二次重试机制不会运行
System.out.println("重跑第【"+currentRetryCount+"】次!");
currentRetryCount++;
return true;
}else{
return false;
}
}
/**
* 用于重置失败重跑时的次数,还原到初始化的值
* 如果项目中是使用dataProvider注解来提供用例测试数据参数化的,
* 那么每个@Test执行的时候都会共有重跑的次数。
* 例如:一个参数化用例有 3 组参数,如果全部正确,结果是:全部通过
* 如果第一组参数,第一次失败(第二次成功,这里就用掉了一次重跑的次数,currentRetryCount 就+1了)
* 接着第二组参数每次执行都失败,这个时候currentRetryCount=2, 那么第二组参数也就只会执行一次重跑。
*/
public void reset() {
currentRetryCount = 1;
}
}
再新建一个监听类 TestngListener , 继承 TestListenerAdapter 类,并重写 onTestSuccess 和 onTestFailure 方法:
package com.ggf.testng.listener;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import java.util.Iterator;
/**
* @Description:
* @Author: ggf
* @Date: 2020/07/20
*/
public class TestngListener extends TestListenerAdapter {
@Override
public void onTestSuccess(ITestResult iTestResult) {
super.onTestSuccess(iTestResult);
TestngRetry testngRetry = (TestngRetry)iTestResult.getMethod().getRetryAnalyzer();
testngRetry.reset();
}
@Override
public void onTestFailure(ITestResult iTestResult) {
super.onTestFailure(iTestResult);
// 每次dataProvider中的参数跑完一次,就重置一次当前的重跑次数,恢复到默认值,保证每个失败的用例都能重跑设置的次数。
TestngRetry testngRetry = (TestngRetry)iTestResult.getMethod().getRetryAnalyzer();
testngRetry.reset();
}
}
再把新建的TestngListener 监听类,配置到 xml 文件中:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="retry">
<test name="retryTest">
<classes>
<class name="com.ggf.testng.listener.RetryDemo"></class>
</classes>
</test>
<listeners>
<!--失败重试监听器-->
<listener class-name="com.ggf.testng.listener.RetryListener"></listener>
<!--用例执行结果监听-->
<listener class-name="com.ggf.testng.listener.TestngListener"></listener>
</listeners>
</suite>
再次执行的结果:

以上就是对于解决使用了dataProvider用例中的每一个参数化用例,在不重置的情况下,用例重跑次数共用的问题。
最后我们加上重置操作后,失败的用例都会重跑跑 2 次,无论最后成功还是失败,都会重置 TestngRetry 中的currentRetryCount 以保证下一个参数化用例开始时,currentRetryCount 为初始状态。
参考文章:https://ntflc.com/2018/10/18/TestNg-Retry-Failed-Tests-with-DataProvider/
TestNg失败重跑—解决使用 dataProvider 参数化用例次数冲突问题的更多相关文章
- testng失败重跑
重跑失败场景 1.要添加两个文件 背景:因为这里只是想单独展示失败的重跑的案例,所以先暂时把app这块的运行注释掉,只跑一个简单的demo,就一个简单类,类中就3个测试方法,失败重跑的原理是,运行方法 ...
- testng增加失败重跑机制
注: 以下内容引自 http://www.yeetrack.com/?p=1015 testng增加失败重跑机制 Posted on 2014 年 10 月 31 日 使用Testng框架搭建自动测试 ...
- testng优化:失败重跑,extentReport+appium用例失败截图,测试报告发邮件
生成的单html方便jenkins集成发邮件,= = 构建失败发邮件 参考:https://blog.csdn.net/galen2016/article/details/77975965 步骤: 1 ...
- python3 unittest框架失败重跑加截图支持python2,python3
github源码地址下载:https://github.com/GoverSky/HTMLTestRunner_cn.git 解压文件后取出/HTMLTestRunner_cn.py文件丢进C:\Py ...
- pytest文档8-html报告报错截图+失败重跑
前言 做web自动化的小伙伴应该都希望在html报告中展示失败后的截图,提升报告的档次,pytest-html也可以生成带截图的报告. conftest.py 1.失败截图可以写到conftest.p ...
- pytest失败重跑
一.说明 平常在做功能测试的时候,经常会遇到某个模块不稳定,偶然会出现一些bug,对于这种问题我们会针对此用例反复执行多次,最终复现出问题来.自动化运行用例时候,也会出现偶然的bug,可以针对单个用例 ...
- 【转载】扩展Robot Framework,实现失败用例自动再执行(失败重跑)
使用自动化脚本进行测试,经常受环境影响等各方面导致本能成功的脚本失败,下面介绍了RFS框架下,失败重跑的方法: 通过改写RobotFramework源代码增加--retry选项,实现test级别的失败 ...
- RF实现多次失败重跑结果合并的基础方法和优化方法
实现思路:通过分次执行失败案例重跑,然后通过结果文件合并命令实现多次失败重跑结果文件的合并,并输出合并后的log和report文件: 说明:具体失败案例重跑命令和结果文件合并命令请参考本博客其他相关章 ...
- pytest 失败重跑截图
1.环境准备 /*@param: 作者:流浪的python Date:2019/01/19 env:python 3.7(由于3.0-3.5以下部分pytest可能有部分兼容问题安装建议2.7-2.9 ...
随机推荐
- Beta冲刺<1/10>
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 Beta冲刺 这个作业的目标 Beta冲刺--第一天(05.19) 作业正文 如下 其他参考文献 ... B ...
- C语言副本机制
1.除了数组外,其他都有副本机制(包括结构体数组) 2.结构体作为参数具有副本机制,结构体返回值也有副本机制 . 3.函数的参数和返回值都有他的副本机制. #include<stdio.h> ...
- 个人作业——软件工程实践总结&个人技术博客
一. 回望 (1)对比开篇博客你对课程目标和期待,"希望通过实践锻炼,增强软件工程专业的能力和就业竞争力",对比目前的所学所练所得,在哪些方面达到了你的期待和目标,哪些方面还存在哪 ...
- Java 数组最佳指南,快收藏让它吃灰
两年前,我甚至写过一篇文章,吐槽数组在 Java 中挺鸡肋的,因为有 List 谁用数组啊,现在想想那时候的自己好幼稚,好可笑.因为我只看到了表面现象,实际上呢,List 的内部仍然是通过数组实现的, ...
- 4. union-find算法
算法的主题思想: 1.优秀的算法因为能够解决实际问题而变得更为重要: 2.高效算法的代码也可以很简单: 3.理解某个实现的性能特点是一个挑战: 4.在解决同一个问题的多种算法之间进行选择时,科学方法是 ...
- python实用笔记——IO编程
打开文件 f = open('/Users/michael/test.txt', 'r') 再读取 >>> f.read() 'Hello, world!' 最后关闭 >> ...
- Python实用笔记——错误处理
让我们用一个例子来看看try的机制: try: print('try...') r = 10 / 0 print('result:', r) except ZeroDivisionError as e ...
- js语法基础入门(3)
3.数据类型 3.1.数据类型学习重点 前面我们通俗的讲了,数据类型其实就是对数据进行了分类,那么,在js中到底把数据分成了几类?这些类的名称叫什么?每个分类下面有那些值?这些问题是需要记清楚的,例如 ...
- 119.杨辉三角II
这道题和第118题是一样的,需要注意这道题目对行数的要求 # 定义一个列表,用来存放数据 num_list = [] for index1 in ran ...
- 洛谷 P2648 赚钱
这道题其实就是求最长路顺便再判断一下正环而已. 这种题肯定要用SPFA的啦,有又正边权(因为最长路所以正边就相当于负边),又是正环(同理,相当于负环),SPFA专治这种问题. 当一个点入队多次的时候, ...