TestNG 失败重跑和截图原理

发表于:2017-11-09 15:54

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:米阳MeYoung    来源:51Testing软件测试网采编

#
TestNG
分享:
  背景
  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 生成多余数据。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号