zoukankan      html  css  js  c++  java
  • 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的方法,会共用TestngRetrycurrentRetryCount,即整个方法的所有参数化用例,总共只会重跑 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 类,并重写 onTestSuccessonTestFailure 方法:

    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/

  • 相关阅读:
    P1144 最短路计数 题解 最短路应用题
    C++高精度加减乘除模板
    HDU3746 Teacher YYF 题解 KMP算法
    POJ3080 Blue Jeans 题解 KMP算法
    POJ2185 Milking Grid 题解 KMP算法
    POJ2752 Seek the Name, Seek the Fame 题解 KMP算法
    POJ2406 Power Strings 题解 KMP算法
    HDU2087 剪花布条 题解 KMP算法
    eclipse创建maven项目(详细)
    maven的作用及优势
  • 原文地址:https://www.cnblogs.com/tester-ggf/p/13345850.html
Copyright © 2011-2022 走看看