侧边栏壁纸
博主头像
一朵云的博客博主等级

拥抱生活,向阳而生。

  • 累计撰写 67 篇文章
  • 累计创建 25 个标签
  • 累计收到 7 条评论

目 录CONTENT

文章目录

TestNG -- 失败用例重跑机制(1)

一朵云
2021-03-27 / 0 评论 / 1 点赞 / 10509 阅读 / 10513 字

介绍

  在接口自动化测试过程中,为了避免因客观原因导致测试用例执行失败,可以考虑为其添加失败就重跑策略。

  我们的策略大致可分为两种:

  • 1、用例执行失败后,记录下来,等用例都跑完后再进行重跑。
  • 2、用例执行失败后,立即进行重跑。

策略一

Git仓库地址:
完整代码可参考仓库,避免可能描述不清
https://gitee.com/a_cloud/my_testng

1、生成 testng-failed.xml 错误文件。

  官方TestNG自带的测试报告中,有一个名为 “testng-failed.xml” 的文件,该文件内便包含了失败的测试用例,我们要重跑的便是它。

  将所有的测试类,放在同一suite套件中,这样的话,TestNG生成的 “test-output” 文件夹下的 “testng-failed.xml” 就会包含所有的失败方法。后续,我们只需要检测 “test-output” 文件夹下是否有“testng-failed.xml”文件,有则重跑即可(重跑完需移除或重命名该文件,避免遗留文件影响二次运行)。

  直接使用TestNG的run()方法后,我们可以发现TestNG默认自带的测试报告,除了会生成我们想要的“testng-failed.xml”文件,还会携带许多我们不需要的“垃圾”文件,如下图所示:

image.png

  我们查看源码后可知,官方默认会为我们带上这么多的测试报告类。

image.png

  而实际上,我们想要的 “testng-failed.xml”文件,只需要监听org.testng.reporters.FailedReporter类,所以我们可以禁用TestNG默认监听器,使用自定义的。

import org.testng.TestNG;
import org.testng.reporters.FailedReporter;

import java.util.ArrayList;
import java.util.List;

public class TestNgMain {

    public static void main(String[] args) {
        TestNG testNG = new TestNG();
        //禁用TestNG默认的监听器
        testNG.setUseDefaultListeners(false);
        testNG.addListener(new FailedReporter());

        List list = new ArrayList();
        list.add("./src/main/resources/testng.xml");
        testNG.setTestSuites(list);
        testNG.run();
    }
}

image.png

  上图可看出,相比之前已经简洁了许多,但还是有多余的。查看org.testng.reporters.FailedReporter类源码,发现可以进行重写,这里我重写为“MyFailedReporter”类,其中注释掉一行,就可以去掉多余的套件文件夹。

import org.testng.TestNG;
import com.dc.listener.MyFailedReporter;
import java.util.ArrayList;
import java.util.List;

public class TestNgMain {

    public static void main(String[] args) {
        TestNG testNG = new TestNG();
        //禁用TestNG默认的监听器
        testNG.setUseDefaultListeners(false);
        testNG.addListener(new MyFailedReporter());

        List list = new ArrayList();
        list.add("./src/main/resources/testng.xml");
        testNG.setTestSuites(list);
        testNG.run();
    }
}

image.png

  以上就很简洁的完成想要的效果。

2、编写监听器,在执行完所有测试用例后重跑。

org.testng.IExecutionListener 监听类,可以监听所有测试用例执行完的时机,所以我们基于该监听器进行错误用例重跑逻辑。

  自定义一个 MyTestNGListener 监听器实现 IExecutionListener

package com.dc.listener;

import com.dc.util.CountUtil;
import lombok.extern.slf4j.Slf4j;
import org.testng.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author xcj
 * @date 2022/5/30 10:54
 */
@Slf4j
public class MyTestNGListener implements IExecutionListener {

    static Date date = new Date();
    static String form = String.format("%tF", date);
    static String hour = String.format("%tH", date);
    static String minute = String.format("%tM", date);
    static String second = String.format("%tS", date);

    /**
     * TestNG重跑机制,当所有用例跑完后,重跑失败的testng-failer.xml文件
     */
    public void onExecutionFinish() {
        //自定义一个CountUtil类,单例模式,用于计算执行次数
        int count = CountUtil.getInstance().getCount();

        String indexPath = "./test-output/index.html";
        String newIndexPath = "./test-output/all_index_" + form + hour + minute + second + ".html";
        String restartIndexPath = "./test-output/index_restart_" + form + hour + minute + second + ".html";
        String failedPath = "./test-output/testng-failed.xml";
        String newFailedPath = "./test-output/all_testng-failed_" + form + hour + minute + second + ".xml";
        String restartFailedPath = "./test-output/testng-failed_restart_" + form + hour + minute + second + ".xml";

        File indeFile = new File(indexPath);
        File failedFile = new File(failedPath);

        //count设置重跑次数
        if (count == 1 && failedFile.exists() && indeFile.exists()) {
            log.info("testng-failed.xml文件已存在,开始重跑失败用例!");

            //重命名FailedReport生成的xml,避免后续重跑导致文件被覆盖
            File newFile = new File(newFailedPath);
            //重命名ExtendReport生成的测试报告,避免后续重跑导致文件被覆盖
            File newIndexFile = new File(newIndexPath);
            //StandardCopyOption.REPLACE_EXISTING 表示存在就替换
            try {
                Files.move(indeFile.toPath(), newIndexFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                Files.move(failedFile.toPath(), newFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException e) {
                e.printStackTrace();
            }

            TestNG testNG = new TestNG();
            testNG.setUseDefaultListeners(false);
            testNG.addListener(new MyFailedReporter());
            testNG.addListener(new ExtentTestNGIReporterListener());
            testNG.addListener(new MyTestNGListener());
            List list = new ArrayList();
            list.add(newFailedPath);
            testNG.setTestSuites(list);
            testNG.run();

        } else {
            if (failedFile.exists()) {
                log.info("IExecutionListener onExecutionFinish!   重跑测试用例也失败了!");

                //重命名重跑的测试报告和错误xml
                File restartIndexFile = new File(restartIndexPath);
                File restartFailedFile = new File(restartFailedPath);
                try {
                    Files.move(indeFile.toPath(), restartIndexFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                    Files.move(failedFile.toPath(), restartFailedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                log.info("程序结束!");
            }
        }
    }
}

  编写执行类,添加了重跑监听器

import com.dc.listener.ExtentTestNGIReporterListener;
import com.dc.listener.MyFailedReporter;
import com.dc.listener.MyTestNGListener;
import org.testng.TestNG;
import java.util.ArrayList;
import java.util.List;

public class TestNgMain {

    public static void main(String[] args) {
        TestNG testNG = new TestNG();
        //禁用TestNG默认的监听器
        testNG.setUseDefaultListeners(false);
        testNG.addListener(new MyFailedReporter());
        //ExtentReporter测试报告
        testNG.addListener(new ExtentTestNGIReporterListener());
        //自定义的重跑监听器
        testNG.addListener(new MyTestNGListener());

        List list = new ArrayList();
        list.add("./src/main/resources/testng.xml");
        testNG.setTestSuites(list);
        testNG.run();

    }
}

  执行结果如下图所示:

image.png

3、注意点:

  以上策略不支持嵌套多个suite,假若嵌套了多个suite.xml文件,“testng-failed.xml”文件会被最后一个suite文件的错误内容重写(由于每个suite都会单独执行出testng-failed.xml文件,重名导致之前的生成的内容被覆盖。)

如:
image.png

login_test.xml文件生成的LoginSuite文件夹下的错误文件
image.png

pay_test.xml文件生成的PaySuite文件夹下的错误文件
image.png

all_test.xml文件生成的总错误文件(缺失了LoginSuite的部分)
image.png

解决方案:

  转换个思路,每次重跑前不再去找test-output 文件夹下是否有“testng-failed.xml”文件了,而是去找对应每个suite文件夹下是否有“testng-failed.xml”文件(之前我们自定义的MyFailedReporter中记得放开注释,这里就不展开了。其实用我上面描述的方法,都写在一个suite套件下也挺好!)

image.png

策略二

  由于篇幅过长问题,我将再下一个篇章展开。

TestNG的Listener列表

  TestNG提供了一组预定义的Listener Java接口,这些接口全部继承自TestNG的 ITestNGListener接口。用户创建这些接口的实现类,并把它们加入到 TestNG 中,TestNG便会在测试运行的不同时刻调用这些类中的接口方法:

  • IExecutionListener 监听TestNG运行的启动和停止。
  • IAnnotationTransformer 注解转换器,用于TestNG测试类中的注解。
  • ISuiteListener 测试套件监听器,监听测试套件的启动和停止。
  • ITestListener 测试运行的监听器。
  • IConfigurationListener 监听配置方法相关的接口。
  • IMethodInterceptor 用于修改TestNG即将运行的测试方法列表。
  • IInvokedMethodListener 测试方法拦截监听,用于获取被TestNG调用的在Method的Before 和After方法监听器。该方法只会被配置和测试方法调用。
  • IHookable 若测试类实现了该接口,当@Test方法被发现时,它的run()方法将会被调用来替代@Test方法。这个测试方法通常在IHookCallBack的callback之上调用,比较适用于需要JASS授权的测试类。
  • IReporter 实现该接口可以生成一份测试报告。
1

评论区