TestNG测试用例重跑详解及实践优化

简介:

测试用例运行稳定性是自动化质量的一个重要指标,在运行中需要尽可能的剔除非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

作者:耿燕飞

相关文章
|
3天前
|
敏捷开发 人工智能 Devops
探索自动化测试的高效策略与实践###
当今软件开发生命周期中,自动化测试已成为提升效率、保障质量的关键工具。本文深入剖析了自动化测试的核心价值,探讨了一系列高效策略,包括选择合适的自动化框架、设计可维护的测试脚本、集成持续集成/持续部署(CI/CD)流程,以及有效管理和维护测试用例库。通过具体案例分析,揭示了这些策略在实际应用中的成效,为软件测试人员提供了宝贵的经验分享和实践指导。 ###
|
3天前
|
人工智能 前端开发 测试技术
探索软件测试中的自动化框架选择与优化策略####
本文深入剖析了当前主流的自动化测试框架,通过对比分析各自的优势、局限性及适用场景,为读者提供了一套系统性的选择与优化指南。文章首先概述了自动化测试的重要性及其在软件开发生命周期中的位置,接着逐一探讨了Selenium、Appium、Cypress等热门框架的特点,并通过实际案例展示了如何根据项目需求灵活选用与配置框架,以提升测试效率和质量。最后,文章还分享了若干最佳实践和未来趋势预测,旨在帮助测试工程师更好地应对复杂多变的测试环境。 ####
14 4
|
3天前
|
机器学习/深度学习 人工智能 jenkins
软件测试中的自动化与持续集成实践
在快速迭代的软件开发过程中,自动化测试和持续集成(CI)是确保代码质量和加速产品上市的关键。本文探讨了自动化测试的重要性、常见的自动化测试工具以及如何将自动化测试整合到持续集成流程中,以提高软件测试的效率和可靠性。通过案例分析,展示了自动化测试和持续集成在实际项目中的应用效果,并提供了实施建议。
|
3天前
|
Java 测试技术 持续交付
探索自动化测试在软件开发中的关键作用与实践
在现代软件开发流程中,自动化测试已成为提升产品质量、加速交付速度的不可或缺的一环。本文深入探讨了自动化测试的重要性,分析了其在不同阶段的应用价值,并结合实际案例阐述了如何有效实施自动化测试策略,以期为读者提供一套可操作的实践指南。
|
8天前
|
机器学习/深度学习 前端开发 测试技术
探索软件测试中的自动化测试框架选择与优化策略####
本文深入探讨了在当前软件开发生命周期中,自动化测试框架的选择对于提升测试效率、保障产品质量的重要性。通过分析市场上主流的自动化测试工具,如Selenium、Appium、Jest等,结合具体项目需求,提出了一套系统化的选型与优化策略。文章首先概述了自动化测试的基本原理及其在现代软件开发中的角色变迁,随后详细对比了各主流框架的功能特点、适用场景及优缺点,最后基于实际案例,阐述了如何根据项目特性量身定制自动化测试解决方案,并给出了持续集成/持续部署(CI/CD)环境下的最佳实践建议。 --- ####
|
3天前
|
Web App开发 敏捷开发 测试技术
探索自动化测试的奥秘:从理论到实践
【10月更文挑战第39天】在软件质量保障的战场上,自动化测试是提升效率和准确性的利器。本文将深入浅出地介绍自动化测试的基本概念、必要性以及如何实施自动化测试。我们将通过一个实际案例,展示如何利用流行的自动化测试工具Selenium进行网页测试,并分享一些实用的技巧和最佳实践。无论你是新手还是有经验的测试工程师,这篇文章都将为你提供宝贵的知识,帮助你在自动化测试的道路上更进一步。
|
3天前
|
敏捷开发 Java 测试技术
探索自动化测试:从理论到实践
【10月更文挑战第39天】在软件开发的海洋中,自动化测试是一艘能够带领团队高效航行的船只。本文将作为你的航海图,指引你理解自动化测试的核心概念,并分享一段实际的代码旅程,让你领略自动化测试的魅力和力量。准备好了吗?让我们启航!
|
8天前
|
测试技术 API Android开发
探索软件测试中的自动化框架选择与实践####
本文深入探讨了软件测试领域内,面对众多自动化测试框架时,如何依据项目特性和团队需求做出明智选择,并分享了实践中的有效策略与技巧。不同于传统摘要的概述方式,本文将直接以一段实践指南的形式,简述在选择自动化测试框架时应考虑的核心要素及推荐路径,旨在为读者提供即时可用的参考。 ####
|
8天前
|
缓存 监控 测试技术
全网最全压测指南!教你如何测试和优化系统极限性能
大家好,我是小米。本文将介绍如何在实际项目中进行性能压测和优化,包括单台服务器和集群压测、使用JMeter、监控CPU和内存使用率、优化Tomcat和数据库配置等方面的内容,帮助你在高并发场景下提升系统性能。希望这些实战经验能助你一臂之力!
22 3
|
18天前
|
监控 安全 jenkins
探索软件测试的奥秘:自动化测试框架的搭建与实践
【10月更文挑战第24天】在软件开发的海洋里,测试是确保航行安全的灯塔。本文将带领读者揭开软件测试的神秘面纱,深入探讨如何从零开始搭建一个自动化测试框架,并配以代码示例。我们将一起航行在自动化测试的浪潮之上,体验从理论到实践的转变,最终达到提高测试效率和质量的彼岸。