背景

UI自动化脚本执行过程中存在非常多的不稳定性,例如网络的不稳定,浏览器无响应等等,这些失败往往并不是产品中的错误。那么这时我们往往需要对执行失败的测试用例进行多次重跑,确认其是否确实失败。 那么失败重跑我们可以通过TestNG的功能来实现。

TestNG IRetryAnalyzer

我们来先看看TestNG的IRetryAnalyzer接口,定义如下:

  1. /**
  2. * Interface to implement to be able to have a chance to retry a failed test.
  3. *
  4. * @author tocman@gmail.com (Jeremie Lenfant-Engelmann)
  5. *
  6. */
  7. public interface IRetryAnalyzer {
  8. /**
  9. * Returns true if the test method has to be retried, false otherwise.
  10. *
  11. * @param result The result of the test method that just ran.
  12. * @return true if the test method has to be retried, false otherwise.
  13. */
  14. public boolean retry(ITestResult result);
  15. }

这个接口只有一个方法:

  1. public boolean retry(ITestResult result);

一旦测试方法失败,就会调用此方法。如果您想重新执行失败的测试用例,那么就让此方法返回true,如果不想重新执行测试用例,则返回false。
如下我们新建一个TestngRetry类,实现IRetryAnalyzer :

  1. import com.frame.testng.utils.ConfigReader;
  2. import org.apache.log4j.Logger;
  3. import org.testng.IRetryAnalyzer;
  4. import org.testng.ITestResult;
  5. import org.testng.Reporter;
  6. public class TestngRetry implements IRetryAnalyzer {
  7. private static Logger logger = Logger.getLogger(TestngRetry.class);
  8. private int retryCount = 1;
  9. private static int maxRetryCount;
  10. /**
  11. * 读取配置文件的重跑次数
  12. */
  13. static {
  14. ConfigReader config = ConfigReader.getInstance();
  15. maxRetryCount = config.getRetryCount();
  16. logger.info("retrycount=" + maxRetryCount);
  17. logger.info("sourceCodeDir=" + config.getSourceCodeDir());
  18. logger.info("sourceCodeEncoding=" + config.getSrouceCodeEncoding());
  19. }
  20. /**
  21. * 重跑机制 设置
  22. *
  23. * @param result
  24. * @return
  25. */
  26. @Override
  27. public boolean retry(ITestResult result) {
  28. if (retryCount <= maxRetryCount) {
  29. String message = "Retry for [" + result.getName() + "] on class [" + result.getTestClass().getName() + "] Retry "
  30. + retryCount + " times";
  31. logger.info(message);
  32. Reporter.setCurrentTestResult(result);
  33. Reporter.log("RunCount=" + (retryCount + 1));
  34. retryCount++;
  35. return true;
  36. }
  37. return false;
  38. }
  39. }

理论上这样我们就已经完成失败重跑的代码编写,但是还不够我们还希望失败自动化截图。

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:

  1. import com.aventstack.extentreports.ExtentTest;
  2. import com.frame.action.ScreenShot;
  3. import org.apache.log4j.Logger;
  4. import org.testng.ITestContext;
  5. import org.testng.ITestResult;
  6. import org.testng.TestListenerAdapter;
  7. import java.util.*;
  8. public class TestResultListener extends TestListenerAdapter {
  9. private static Logger logger = Logger.getLogger(TestResultListener.class);
  10. private static ThreadLocal<ExtentTest> test = new ThreadLocal();
  11. @Override
  12. public void onTestFailure(ITestResult tr) {
  13. super.onTestFailure(tr);
  14. // 自动截图
  15. ScreenShot.screenShots();
  16. logger.info(tr.getName() + " Failure");
  17. }
  18. @Override
  19. public void onTestSkipped(ITestResult tr) {
  20. super.onTestSkipped(tr);
  21. // 自动截图
  22. ScreenShot.screenShots();
  23. logger.info(tr.getName() + " Skipped");
  24. }
  25. @Override
  26. public void onTestSuccess(ITestResult tr) {
  27. super.onTestSuccess(tr);
  28. logger.info(tr.getName() + " Success");
  29. }
  30. @Override
  31. public void onTestStart(ITestResult tr) {
  32. super.onTestStart(tr);
  33. logger.info(tr.getName() + " Start");
  34. }
  35. @Override
  36. public void onFinish(ITestContext testContext) {
  37. super.onFinish(testContext);
  38. // List of test results which we will delete later
  39. ArrayList<ITestResult> testsToBeRemoved = new ArrayList<ITestResult>();
  40. // collect all id's from passed test
  41. Set<Integer> passedTestIds = new HashSet<Integer>();
  42. for (ITestResult passedTest : testContext.getPassedTests().getAllResults()) {
  43. logger.info("PassedTests = " + passedTest.getName());
  44. passedTestIds.add(getId(passedTest));
  45. }
  46. // Eliminate the repeat methods
  47. Set<Integer> skipTestIds = new HashSet<Integer>();
  48. for (ITestResult skipTest : testContext.getSkippedTests().getAllResults()) {
  49. logger.info("skipTest = " + skipTest.getName());
  50. // id = class + method + dataprovider
  51. int skipTestId = getId(skipTest);
  52. if (skipTestIds.contains(skipTestId) || passedTestIds.contains(skipTestId)) {
  53. testsToBeRemoved.add(skipTest);
  54. } else {
  55. skipTestIds.add(skipTestId);
  56. }
  57. }
  58. // Eliminate the repeat failed methods
  59. Set<Integer> failedTestIds = new HashSet<Integer>();
  60. for (ITestResult failedTest : testContext.getFailedTests().getAllResults()) {
  61. logger.info("failedTest = " + failedTest.getName());
  62. // id = class + method + dataprovider
  63. int failedTestId = getId(failedTest);
  64. // if we saw this test as a failed test before we mark as to be
  65. // deleted
  66. // or delete this failed test if there is at least one passed
  67. // version
  68. if (failedTestIds.contains(failedTestId) || passedTestIds.contains(failedTestId) ||
  69. skipTestIds.contains(failedTestId)) {
  70. testsToBeRemoved.add(failedTest);
  71. } else {
  72. failedTestIds.add(failedTestId);
  73. }
  74. }
  75. // finally delete all tests that are marked
  76. for (Iterator<ITestResult> iterator = testContext.getFailedTests().getAllResults().iterator(); iterator.hasNext();) {
  77. ITestResult testResult = iterator.next();
  78. if (testsToBeRemoved.contains(testResult)) {
  79. logger.info("Remove repeat Fail Test: " + testResult.getName());
  80. iterator.remove();
  81. }
  82. }
  83. }
  84. private int getId(ITestResult result) {
  85. int id = result.getTestClass().getName().hashCode();
  86. id = id + result.getMethod().getMethodName().hashCode();
  87. id = id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0);
  88. return id;
  89. }
  90. }

其实我们就是重写了
onTestFailure() 和 onTestSkipped() 方法,添加上截图方法,当然上面我们还重写了onFinish()方法,目的是最后生成report时,移除重跑的结果,避免report 生成多余数据。