测试用例运行稳定性是自动化质量的一个重要指标,在运行中需要尽可能的剔除非bug造成的测试用例执行失败,对于失败用例进行重跑是常用策略之一。一种重跑策略是所有用例运行结束后对失败用例重跑,另一种重跑策略是在运行时监控用例运行状态,失败后实时重跑。

下面,详细介绍TestNG如何对失败测试用例实时重跑并解决重跑过程中所遇到问题的实践和解决方案。对失败测试用例进行实时重跑,有以下几个方面需求:

  1. 测试用例运行失败,监听到失败后立即进行重跑
  2. 测试用例通过dependsOnMethods/dependsOnGroups标记依赖其他测试用例,在被依赖的测试用例重跑运行成功后,该测试用例可以继续运行
  3. 对于重跑多次的测试用例,只记录最后一次运行成功或失败结果

第一部分 测试用例重跑

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的运行代码,其在对失败运行用例重跑时,逻辑如下图。



对于通过dependsOnMethodsdependsOnGroups注解依赖于其他测试用例的测试用例来讲,测试用例执行分为两种情况:

  • 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,下面的代码通过重写TestListenerAdapteronTestSuccess()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测试用例重跑详解及实践优化的更多相关文章

  1. Java自动化测试框架-12 - TestNG之xml文件详解篇 (详细教程)

    1.简介 现在这篇,我们来学习TestNG.xml文件,前面我们已经知道,TestNG就是运行这个文件来执行测试用例的.通过本篇,你可以进一步了解到:这个文件是配置测试用例,测试套件.简单来说,利用这 ...

  2. 《手把手教你》系列基础篇(八十三)-java+ selenium自动化测试-框架设计基础-TestNG测试报告-下篇(详解教程)

    1.简介 其实前边好像简单的提到过测试报告,宏哥觉得这部分比较重要,就着重讲解和介绍一下.报告是任何测试执行中最重要的部分,因为它可以帮助用户了解测试执行的结果.失败点和失败原因.另一方面,日志记录对 ...

  3. 小甲鱼PE详解之基址重定位详解(PE详解10)

    今天有一个朋友发短消息问我说“老师,为什么PE的格式要讲的这么这么细,这可不是一般的系哦”.其实之所以将PE结构放在解密系列继基础篇之后讲并且尽可能细致的讲,不是因为小甲鱼没事找事做,主要原因是因为P ...

  4. TestNg失败重跑—解决使用 dataProvider 参数化用例次数冲突问题

    问题背景 在使用 testng 执行 UI 自动化用例时,由于 UI自动化的不稳定性,我们在测试的时候,往往会加上失败重跑机制.在不使用 @DataProvider 提供用例参数化时,是不会有什么问题 ...

  5. 《手把手教你》系列基础篇(八十四)-java+ selenium自动化测试-框架设计基础-TestNG日志-上篇(详解教程)

    1.简介 TestNG还为我们提供了测试的记录功能-日志.例如,在运行测试用例期间,用户希望在控制台中记录一些信息.信息可以是任何细节取决于目的.牢记我们正在使用Selenium进行测试,我们需要有助 ...

  6. SVM-支持向量机原理详解与实践

    前言 去年由于工作项目的需要实际运用到了SVM和ANN算法,也就是支持向量机和人工神经网络算法,主要是实现项目中的实时采集图片(工业高速摄像头采集)的图像识别的这一部分功能,虽然几经波折,但是还好最终 ...

  7. testng失败重跑

    重跑失败场景 1.要添加两个文件 背景:因为这里只是想单独展示失败的重跑的案例,所以先暂时把app这块的运行注释掉,只跑一个简单的demo,就一个简单类,类中就3个测试方法,失败重跑的原理是,运行方法 ...

  8. .NET ORM框架 SqlSuagr4.0 功能详解与实践【开源】

    SqlSugar 4.0 ORM框架的优势 为了未来能够更好的支持多库分布式的存储,并行计算等功能,将SqlSugar3.x全部重写,现有的架构可以轻松扩展多库. 源码下载: https://gith ...

  9. [动图演示]Redis 持久化 RDB/AOF 详解与实践

    Redis 是一个开源( BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.它支持的数据类型很丰富,如字符串.链表.集 合.以及散列等,并且还支持多种排序功能. 什么叫持 ...

随机推荐

  1. AJ学IOS 之控制器view显示中view的父子关系及controller的父子关系_解决屏幕旋转不能传递事件问题

    AJ分享,必须精品 一:效果 二:项目代码 这个Demo用的几个控制器分别画了不通的xib,随便拖拽了几个空间,主要是几个按钮的切换,主要代码展示下: // // NYViewController.m ...

  2. Mac 下 brew 切换为国内源

    简介 Homebrew 是一款自由及开放源代码的软件包管理系统,用以简化 macOS 和 linux 系统上的软件安装过程.它拥有安装.卸载.更新.查看.搜索等很多实用的功能,通过简单的一条指令,就可 ...

  3. 068.Python框架Django之DRF视图集使用

    一 视图集与路由的使用 使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中: list() 提供一组数据 retrieve() 提供单个数据 create() 创建数据 update() ...

  4. Mysql数据导入导出功能(设置及使用)

    使用Mysql自带的outfile语法,将查询结果导成excel格式. 1.OUTFILE介绍及常见问题解决: )查询数据导出成csv 直接使用mysql导出csv方法 我们可以使用 into out ...

  5. 详解 方法的覆盖 —— toString() 与 equals()的覆盖

    在学习本篇博文前,建议先学习完本人的博文--<详解 继承(上)-- 工具的抽象与分层> 在本人之前的博文中曾讲过"基类"的知识,那么,本篇博文中的主题--Object类 ...

  6. 9. js屏幕截图

    html2canvas 该脚本允许您直接在用户浏览器上截取网页或部分网页的“屏幕截图”.屏幕截图基于DOM,因此它可能不是真实表示的100%准确,因为它没有制作实际的屏幕截图,而是根据页面上可用的信息 ...

  7. XCTF两个PHP代码审计的笔记

    题目源码如下,考点是输入的$id和$row['id']的区别 关键在于红框内,可以知道题目的payload是要让$row['id']存在,查询的到数据,并且要让POST的id不能与adog相同.那显而 ...

  8. Unity 游戏框架搭建 2019 (三十二、三十三) 类的命名 & 代码文件命名

    昨天我们完成了第八个示例的第二个 MenuItem 菜单顺序的调整. 我们今天再往下接着调整. 我们来看下接下来的 MenuItem 代码如下: [MenuItem("QFramework/ ...

  9. 基于udp协议的套接字通信

    服务端: import socket server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) server.bind(('127.0.0.1',8 ...

  10. c++动态数组的使用

    在c++中,有的时候会遇到变长的数组(不管是一维的还是二维的),这个时候就需要用到动态数组了,并且要用new和delete两个操作符,这俩操作符一般成对使用. 先说一维的动态数组吧,直接上代码 #in ...