背景
UI自动化脚本执行过程中存在非常多的不稳定性,例如网络的不稳定,浏览器无响应等等,这些失败往往并不是产品中的错误。那么这时我们往往需要对执行失败的测试用例进行多次重跑,确认其是否确实失败。 那么失败重跑我们可以通过TestNG的功能来实现。
TestNG IRetryAnalyzer
我们来先看看TestNG的IRetryAnalyzer接口,定义如下:
/*** Interface to implement to be able to have a chance to retry a failed test.** @author tocman@gmail.com (Jeremie Lenfant-Engelmann)**/public interface IRetryAnalyzer {/*** Returns true if the test method has to be retried, false otherwise.** @param result The result of the test method that just ran.* @return true if the test method has to be retried, false otherwise.*/public boolean retry(ITestResult result);}
这个接口只有一个方法:
public boolean retry(ITestResult result);
一旦测试方法失败,就会调用此方法。如果您想重新执行失败的测试用例,那么就让此方法返回true,如果不想重新执行测试用例,则返回false。
如下我们新建一个TestngRetry类,实现IRetryAnalyzer :
import com.frame.testng.utils.ConfigReader;import org.apache.log4j.Logger;import org.testng.IRetryAnalyzer;import org.testng.ITestResult;import org.testng.Reporter;public class TestngRetry implements IRetryAnalyzer {private static Logger logger = Logger.getLogger(TestngRetry.class);private int retryCount = 1;private static int maxRetryCount;/*** 读取配置文件的重跑次数*/static {ConfigReader config = ConfigReader.getInstance();maxRetryCount = config.getRetryCount();logger.info("retrycount=" + maxRetryCount);logger.info("sourceCodeDir=" + config.getSourceCodeDir());logger.info("sourceCodeEncoding=" + config.getSrouceCodeEncoding());}/*** 重跑机制 设置** @param result* @return*/@Overridepublic boolean retry(ITestResult result) {if (retryCount <= maxRetryCount) {String message = "Retry for [" + result.getName() + "] on class [" + result.getTestClass().getName() + "] Retry "+ retryCount + " times";logger.info(message);Reporter.setCurrentTestResult(result);Reporter.log("RunCount=" + (retryCount + 1));retryCount++;return true;}return false;}}
理论上这样我们就已经完成失败重跑的代码编写,但是还不够我们还希望失败自动化截图。
TestNG Listener
TestNG的监听类很多,这里我们可以直接继承TestListenerAdapter类,并做个重写已实现失败自动化截图操作。
TestListenerAdapter 实现IResultListener2 接口,IResultListener2 又继承了IResultListener类,IResultListener又继承了ITestListener类。
那我们直接找到TestNG的ITestListener类查看下他的源代码,我们会发现里面有如下一些方法:
onFinish():在所有测试运行之后调用,并且所有的配置方法都被调用
onStart(): 在测试类被实例化之后调用,并在调用任何配置方法之前调用。
onTestFailedButWithinSuccessPercentage(ITestResult context): 每次方法失败但是已经使用successPercentage进行注释时调用,并且此失败仍保留在请求的成功百分比之内。
onTestFailure(ITestResult context):每次测试失败时调用。
onTestSkipped(ITestResult context): 每次跳过测试时调用
onTestStart(ITestResult context): 每次调用测试之前调用。
onTestSuccess(ITestResult context): 每次测试成功时调用
那么我们想要实现失败自动截图,我们只需要重写TestListenerAdapter 里面的方法便可。
例如我们新建个TestResultListener继承TestListenerAdapter:
import com.aventstack.extentreports.ExtentTest;import com.frame.action.ScreenShot;import org.apache.log4j.Logger;import org.testng.ITestContext;import org.testng.ITestResult;import org.testng.TestListenerAdapter;import java.util.*;public class TestResultListener extends TestListenerAdapter {private static Logger logger = Logger.getLogger(TestResultListener.class);private static ThreadLocal<ExtentTest> test = new ThreadLocal();@Overridepublic void onTestFailure(ITestResult tr) {super.onTestFailure(tr);// 自动截图ScreenShot.screenShots();logger.info(tr.getName() + " Failure");}@Overridepublic void onTestSkipped(ITestResult tr) {super.onTestSkipped(tr);// 自动截图ScreenShot.screenShots();logger.info(tr.getName() + " Skipped");}@Overridepublic void onTestSuccess(ITestResult tr) {super.onTestSuccess(tr);logger.info(tr.getName() + " Success");}@Overridepublic void onTestStart(ITestResult tr) {super.onTestStart(tr);logger.info(tr.getName() + " Start");}@Overridepublic void onFinish(ITestContext testContext) {super.onFinish(testContext);// List of test results which we will delete laterArrayList<ITestResult> testsToBeRemoved = new ArrayList<ITestResult>();// collect all id's from passed testSet<Integer> passedTestIds = new HashSet<Integer>();for (ITestResult passedTest : testContext.getPassedTests().getAllResults()) {logger.info("PassedTests = " + passedTest.getName());passedTestIds.add(getId(passedTest));}// Eliminate the repeat methodsSet<Integer> skipTestIds = new HashSet<Integer>();for (ITestResult skipTest : testContext.getSkippedTests().getAllResults()) {logger.info("skipTest = " + skipTest.getName());// id = class + method + dataproviderint skipTestId = getId(skipTest);if (skipTestIds.contains(skipTestId) || passedTestIds.contains(skipTestId)) {testsToBeRemoved.add(skipTest);} else {skipTestIds.add(skipTestId);}}// Eliminate the repeat failed methodsSet<Integer> failedTestIds = new HashSet<Integer>();for (ITestResult failedTest : testContext.getFailedTests().getAllResults()) {logger.info("failedTest = " + failedTest.getName());// id = class + method + dataproviderint failedTestId = getId(failedTest);// if we saw this test as a failed test before we mark as to be// deleted// or delete this failed test if there is at least one passed// versionif (failedTestIds.contains(failedTestId) || passedTestIds.contains(failedTestId) ||skipTestIds.contains(failedTestId)) {testsToBeRemoved.add(failedTest);} else {failedTestIds.add(failedTestId);}}// finally delete all tests that are markedfor (Iterator<ITestResult> iterator = testContext.getFailedTests().getAllResults().iterator(); iterator.hasNext();) {ITestResult testResult = iterator.next();if (testsToBeRemoved.contains(testResult)) {logger.info("Remove repeat Fail Test: " + testResult.getName());iterator.remove();}}}private int getId(ITestResult result) {int id = result.getTestClass().getName().hashCode();id = id + result.getMethod().getMethodName().hashCode();id = id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0);return id;}}
其实我们就是重写了
onTestFailure() 和 onTestSkipped() 方法,添加上截图方法,当然上面我们还重写了onFinish()方法,目的是最后生成report时,移除重跑的结果,避免report 生成多余数据。
