背景
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 */ @Override public 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(); @Override public void onTestFailure(ITestResult tr) { super.onTestFailure(tr); // 自动截图 ScreenShot.screenShots(); logger.info(tr.getName() + " Failure"); } @Override public void onTestSkipped(ITestResult tr) { super.onTestSkipped(tr); // 自动截图 ScreenShot.screenShots(); logger.info(tr.getName() + " Skipped"); } @Override public void onTestSuccess(ITestResult tr) { super.onTestSuccess(tr); logger.info(tr.getName() + " Success"); } @Override public void onTestStart(ITestResult tr) { super.onTestStart(tr); logger.info(tr.getName() + " Start"); } @Override public void onFinish(ITestContext testContext) { super.onFinish(testContext); // List of test results which we will delete later ArrayList<ITestResult> testsToBeRemoved = new ArrayList<ITestResult>(); // collect all id's from passed test Set<Integer> passedTestIds = new HashSet<Integer>(); for (ITestResult passedTest : testContext.getPassedTests().getAllResults()) { logger.info("PassedTests = " + passedTest.getName()); passedTestIds.add(getId(passedTest)); } // Eliminate the repeat methods Set<Integer> skipTestIds = new HashSet<Integer>(); for (ITestResult skipTest : testContext.getSkippedTests().getAllResults()) { logger.info("skipTest = " + skipTest.getName()); // id = class + method + dataprovider int skipTestId = getId(skipTest); if (skipTestIds.contains(skipTestId) || passedTestIds.contains(skipTestId)) { testsToBeRemoved.add(skipTest); } else { skipTestIds.add(skipTestId); } } // Eliminate the repeat failed methods Set<Integer> failedTestIds = new HashSet<Integer>(); for (ITestResult failedTest : testContext.getFailedTests().getAllResults()) { logger.info("failedTest = " + failedTest.getName()); // id = class + method + dataprovider int 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 // version if (failedTestIds.contains(failedTestId) || passedTestIds.contains(failedTestId) || skipTestIds.contains(failedTestId)) { testsToBeRemoved.add(failedTest); } else { failedTestIds.add(failedTestId); } } // finally delete all tests that are marked for (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 生成多余数据。