介绍
在接口自动化测试过程中,为了避免因客观原因导致测试用例执行失败,可以考虑为其添加失败就重跑策略。
我们的策略大致可分为两种:
- 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”文件,还会携带许多我们不需要的“垃圾”文件,如下图所示:
我们查看源码后可知,官方默认会为我们带上这么多的测试报告类。
而实际上,我们想要的 “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();
}
}
上图可看出,相比之前已经简洁了许多,但还是有多余的。查看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();
}
}
以上就很简洁的完成想要的效果。
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();
}
}
执行结果如下图所示:
3、注意点:
以上策略不支持
如:
login_test.xml文件生成的LoginSuite文件夹下的错误文件
pay_test.xml文件生成的PaySuite文件夹下的错误文件
all_test.xml文件生成的总错误文件(缺失了LoginSuite的部分)
解决方案:
转换个思路,每次重跑前不再去找test-output 文件夹下是否有“testng-failed.xml”文件了,而是去找对应每个suite文件夹下是否有“testng-failed.xml”文件(之前我们自定义的MyFailedReporter中记得放开注释,这里就不展开了。其实用我上面描述的方法,都写在一个suite套件下也挺好!)
策略二
由于篇幅过长问题,我将再下一个篇章展开。
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 实现该接口可以生成一份测试报告。
评论区