Testng源码阅读

2021-06-27
halley.fang

TestNG是一个测试框架,旨在简化广泛的测试需求,从单元测试(在其他类中隔离测试一个类)到集成测试(测试由几个类、几个包甚至几个外部框架组成的整个系统,如应用程序服务器)。

Testng 源码阅读

先来看一下调用方法:

        TestNG tng = new TestNG();
        tng.setTestClasses(TestPlan.getTestPlan(classes));
        tng.setParallel(XmlSuite.ParallelMode.CLASSES);
        tng.setThreadCount(1);
        tng.addListener(new AssertionListener());
        tng.addListener(new TestListenerAdapter());
        tng.addListener(new RePrioritizingListener());
        tng.addListener(new ReportListener());
        tng.addListener(new SendResultEmailListener());
        tng.run();

首先new TestNG对象,然后设置待测的用例、线程参数、监听器等,然后调用run方法运行。

new TestNG对象

private void init(boolean useDefaultListeners) {
  m_instance = this;

  m_useDefaultListeners = useDefaultListeners;//启用默认监听器
  m_configuration = new Configuration();//初始化配置
}

初始化配置主要是排除@Ignore注解的类和方法和默认的注解都放入m_annotationFinder(例如@Test @BeforeClass)

public Configuration() {
  init(new JDK15AnnotationFinder(new DefaultAnnotationTransformer()));
}

private void init(IAnnotationFinder finder) {
  m_annotationFinder = finder;
}

配置待测用例运行环境参数

配置待测用例运行环境参数主要就是TestNG提供给我们自己配置的一些参数,例如传入的用例是Suite方式还是Class方式,线程配置,使用哪些监听器等,具体的方法就不一一列举了,详情可以参考TestNG官方文档或者源码。

Run TestNG

重点就是要看一下TestNG是如何运行的,下面是run方法:

public void run() {
    initializeEverything();(1)
    sanityCheck();(2)

    runExecutionListeners(true /* start */);(3)

    runSuiteAlterationListeners();(4)

    m_start = System.currentTimeMillis();//开始执行时间
    List<ISuite> suiteRunners = runSuites();(5)

    m_end = System.currentTimeMillis();//执行结束时间

    if(null != suiteRunners) {
      generateReports(suiteRunners);(6)
    }

    runExecutionListeners(false /* finish */);(7)
    exitCode = this.exitCodeListener.getStatus();(8)

    if(!exitCodeListener.hasTests()) {
      if (TestRunner.getVerbose() > 1) {
        System.err.println("[TestNG] No tests found. Nothing was run");
        usage();
      }
    }

    m_instance = null;//清除
    m_jCommander = null;//清除
  }
1 初始化
public void initializeEverything() {
  // The Eclipse plug-in (RemoteTestNG) might have invoked this method already
  // so don't initialize suites twice.
  if (m_isInitialized) {
    return;
  }

  initializeSuitesAndJarFile();//初始化Suites和jar包,解析并存入m_suites
  initializeConfiguration();//初始化配置,加载ITestNGListener类型和m_suites中的监听器,将测试方法加入m_methodDescriptors,设置setAnnotationFinder为new 对象是初始化的finder
  initializeDefaultListeners();//初始化默认监听器m_useDefaultListeners
  initializeCommandLineSuites();//初始化通过命令行指定的测试类或方法
  initializeCommandLineSuitesParams();//初始化通过命令行指定的参数
  initializeCommandLineSuitesGroups();//初始化通过命令行指定的组

  m_isInitialized = true;//标记已经初始化
}
2 在执行套件之前,进行完整性检查,以确保满足所有所需条件。主要是验证是否有测试方法重名,重名了则抛异常。
3 执行监听器,传入start
private void runExecutionListeners(boolean start) {
  for (IExecutionListener l : m_configuration.getExecutionListeners()) {
    if (start) {
      l.onExecutionStart();//执行所有监听器@OnStart
    } else {
      l.onExecutionFinish();//执行所有监听器@OnFinish
    }
  }
}
4 执行所有的IAlterSuiteListener,主要是提供了一个修改测试套件的途径
5 正式执行,显示会去为每个测试都创建一个testRunner,最终调用privateRun
private void privateRun() {

    // Map for unicity, Linked for guaranteed order
    Map<Method, ITestNGMethod> beforeSuiteMethods= new LinkedHashMap<>();
    Map<Method, ITestNGMethod> afterSuiteMethods = new LinkedHashMap<>();

    IInvoker invoker = null;

    // Get the invoker and find all the suite level methods
    for (TestRunner tr: testRunners) {
      // TODO: Code smell.  Invoker should belong to SuiteRunner, not TestRunner
      // -- cbeust
      invoker = tr.getInvoker();//获取调用的方法

      for (ITestNGMethod m : tr.getBeforeSuiteMethods()) {
        beforeSuiteMethods.put(m.getConstructorOrMethod().getMethod(), m);//加入beforeSuite
      }

      for (ITestNGMethod m : tr.getAfterSuiteMethods()) {
        afterSuiteMethods.put(m.getConstructorOrMethod().getMethod(), m);//加入afterSuite
      }
    }

    //
    // Invoke beforeSuite methods (the invoker can be null
    // if the suite we are currently running only contains
    // a <file-suite> tag and no real tests)
    //
    if (invoker != null) {
      if(! beforeSuiteMethods.values().isEmpty()) {
        //执行调用beforeSuite
        invoker.invokeConfigurations(null,
            beforeSuiteMethods.values().toArray(new ITestNGMethod[beforeSuiteMethods.size()]),
            xmlSuite, xmlSuite.getParameters(), null, /* no parameter values */
            null /* instance */
        );
      }

      Utils.log("SuiteRunner", 3, "Created " + testRunners.size() + " TestRunners");

      //
      // Run all the test runners
      //
      boolean testsInParallel = XmlSuite.ParallelMode.TESTS.equals(xmlSuite.getParallel());
      if (!testsInParallel) {
        runSequentially();//串行执行
      }
      else {
        runInParallelTestMode();//并行执行
      }

      //
      // Invoke afterSuite methods
      //
      if (! afterSuiteMethods.values().isEmpty()) {
        //调用afterSuite
        invoker.invokeConfigurations(null,
              afterSuiteMethods.values().toArray(new ITestNGMethod[afterSuiteMethods.size()]),
            xmlSuite, xmlSuite.getAllParameters(), null, /* no parameter values */

              null /* instance */);
      }
    }
  }
6 生成测试结果报告
7 执行@OnFinish
8 获取退出状态

由于细节太多,都体现在一篇博客中比较困难,本文中有些核心细节被略过了,后续有时间再进行完善补充。


Similar Posts

下一篇 Junit用法

Comments

Table of contents