TestNG测试框架之监听器详解

本文介绍了开源自动化测试框架TestNG,它功能强大、易于使用。详细阐述了TestNG提供的多种监听器,如IAnnotationTransformer、IInvokedMethodListener等,还说明了监听器的使用方法,包括在testng.xml、源代码、命令行等中使用。最后给出动态测试方法过滤和测试进度跟踪两个监听器示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. TestNG简介

2. TestNG监听器概述

2.1 IAnnotationTransformer

示例

2.2 IAnnotationTransformer2

示例

2.3 IHookable

示例1

示例2

2.4 IInvokedMethodListener

示例1

示例2

2.5 IMethodInterceptor

示例

2.6 IReporter

示例1

示例2

2.7 ISuiteListener

2.8 ITestListener

示例1

示例2

3. 监听器的使用方法

3.1 在 testng.xml中使用TestNG监听器

3.2 在源代码中使用TestNG监听器

3.3 通过ServiceLoader使用 TestNG监听器

3.4 通过命令行使用TestNG监听器

3.5 通过 IDE 使用 TestNG 监听器

4. 示例

4.1 动态测试方法过滤监听器(listeners.OSFilter)

4.2 测试进度跟踪监听器(listeners.ProgressTracker)

4.3 如何运行示例代码

4.4 下载代码示例

5. 结束语


1. TestNG简介

TestNG 是一个开源的自动化测试框架,其灵感来自 JUnit 和 NUnit,但它引入了一些新功能,使其功能更强大,更易于使用。TestNG 的设计目标是能够被用于进行各种类型测试:单元测试、功能测试,端到端测试、集成测试等等。NG 是 Next Generation 的简写,表示下一代,意在表示其产生的目的是要超越当前所有测试框架。TestNG 类似于 JUnit(特别是 JUnit 4),但它不是 JUnit 的扩展,而是独立的全新设计的框架。TestNG 的创造者是 Cedric Beust(塞德里克·博伊斯特)。

TestNG 有如下特点:

  • 支持 Java 注释功能
  • 测试运行在任意大的线程池中,并且有多种运行策略可供选择(所有测试方法运行在自己的线程中、每个测试类一个线程,等等)。
  • 线程安全
  • 灵活的测试配置
  • 支持数据驱动测试(通过 @DataProvider 注释)
  • 支持参数化
  • 强大的运行模型(不再使用 TestSuite)
  • 有多种工具和插件支持(Eclipse, IDEA, Maven, 等等)
  • 内嵌 BeanShell 以进一步增强灵活性
  • 默认提供 JDK 的运行时和日志功能
  • 提供应用服务器测试依赖的方法

2. TestNG监听器概述

尽管 TestNG 的默认配置已经提供了不少强大的功能和灵活的选项,但是没有一种方案能够解决所有的问题。在实际应用中,我们多多少少会发现 TestNG 自带的功能无法满足我们的一些实际需求,尤其是关于测试方法运行的行为、报表以及通知功能。此时,我们就需要使用TestNG 的监听器定制额外的功能以满足我们的需要。

以下是 TestNG 提供的几种监听器:

  • IAnnotationTransformer
  • IAnnotationTransformer2
  • IHookable
  • IInvokedMethodListener
  • IMethodInterceptor
  • IReporter
  • ISuiteListener
  • ITestListener

尽管名字叫监听器,但事实上它们只是一些预定义的 Java 接口。用户创建这些接口的实现类,并把它们加入到 TestNG 中,TestNG 便会在测试运行的不同时刻调用这些类中的接口方法。接下来,我们一一介绍 TestNG 中的每种监听器。

2.1 IAnnotationTransformer

大多数情况下,在运行时我们不需要改动源代码中定义的注解,但有时需要这样做。这时,我们就需要使用 IAnnotationTransformer 监听器。IAnnotationTransformer 只能用来修改 @Test 注解,如果需要修改其他 TestNG 的注解(比如,@DataProvider, @Factory 以及 @Configuration),需要使用 IAnnotationTransformer2 监听器。IAnnotationTransformer 要求实现 transform 方法,其方法签名如下:

void transform(ITest annotation, Class testClass, Constructor testConstructor, Method testMethod);

annotation 代表的就是为 testMethod 定义的 @Test 注解。调用其方法可以更改 @Test 注解属性。例如,下面的代码在运行时将属性 enabled 改为 false 从而禁用了当前的测试方法。

annotation.setEnabled(false);

示例

通过javadoc可发现,IAnnotationTransformer继承了ITestNGListener接口,其要求实现transform方法。

public interface IAnnotationTransformer extends ITestNGListener{
 
  /**
   * This method will be invoked by TestNG to give you a chance
   * to modify a TestNG annotation read from your test classes.
   * You can change the values you need by calling any of the
   * setters on the ITest interface.
   *
   * Note that only one of the three parameters testClass,
   * testConstructor and testMethod will be non-null.
   *
   * @param annotation The annotation that was read from your
   * test class.
   * @param testClass If the annotation was found on a class, this
   * parameter represents this class (null otherwise).
   * @param testConstructor If the annotation was found on a constructor,
   * this parameter represents this constructor (null otherwise).
   * @param testMethod If the annotation was found on a method,
   * this parameter represents this method (null otherwise).
   */
  public void transform(ITest annotation, Class testClass,
      Constructor testConstructor, Method testMethod);
}

通过重写transform方法,可改写@Test注解的属性,示例如下:
编写测试类如下:

import org.testng.annotations.Test;

public class TestTransform {

    @Test
    public void test(){
        System.out.println("Test annotationTransformer!");
    }

}

IAnnotationTransformer实现类如下:

import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Transform implements IAnnotationTransformer {
    public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method) {
        iTestAnnotation.setInvocationCount(2); //执行2次
    }
}

testng.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" >
    <listeners>
        <listener class-name="Transform"/>
    </listeners>

    <test verbose="2" preserve-order="true" name="Test">
        <classes>
            <class name="TestTransform">
            </class>
        </classes>
    </test>
</suite>

执行结果如下:

Test annotationTransformer!
Test annotationTransformer!

===============================================
All Test Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

如上结果所示,可发现@Test注解的方法执行了2次,说明Transform监听器起了作用。

2.2 IAnnotationTransformer2

前文已提到 IAnnotationTransformer2 监听器是用来在运行时修改除 @Test 以外的 TestNG 的注解。下面是该监听器要求实现的方法。

void transform(IConfigurationAnnotationannotation, java.lang.ClasstestClass,
 java.lang.reflect.ConstructortestConstructor,
 java.lang.reflect.MethodtestMethod)
void transform(IDataProviderAnnotationannotation, java.lang.reflect.Methodmethod)
void transform(IFactoryAnnotationannotation, java.lang.reflect.Methodmethod)

可见,目前只有 @Configuration,@DataProvider 以及 @Factory 注解能够通过该监听器修改。而事实上,@Configuration 在最新版本中已不被推荐使用,需用 @BeforeSuite,@AfterSuite 等注释替代。

示例

public interface IAnnotationTransformer2 extends IAnnotationTransformer {
    void transform(IConfigurationAnnotation var1, Class var2, Constructor var3, Method var4);

    void transform(IDataProviderAnnotation var1, Method var2);

    void transform(IFactoryAnnotation var1, Method var2);
}

IAnnotationTransformer2继承IAnnotationTransformer接口,其可以用于修改@DataProvider,@Factory,@Configuration注解,但@Configuration在新版本已经被@BeforeSuite,@AfterSuite 等代替,所以此处不再探讨。
编写测试类:

import org.testng.annotations.Test;

public class TestTransform {
    private String str;
    public TestTransform(String str){
        this.str = str;
    }

    @Test()
    public void test(){
        System.out.println("Test annotationTransformer!");
        System.out.println("DataProviderName:"+str);
    }
}

编写工厂类,该工厂类有两个dataProvider(tom,data),默认使用tom。

import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;

public class TransformFactory {
    @Factory(dataProvider = "tom")
    public Object[] transformFac(String str){
        Object[] objects = new Object[1];
        for(int i=0;i<1;i++){
            TestTransform testTransform = new TestTransform(str);
            objects[i] = testTransform;
        }
        return objects;
    }

    @DataProvider(name = "tom")
    public Object[][] tom(){
        return new Object[][]{new Object[]{"tom"}};
    }

    @DataProvider(name = "data")
    public Object[][] data(){
        return new Object[][]{new Object[]{"data"}};
    }
}

编写IAnnotationTransformer2实现类如下,用于修改Factory的dataProvider为data。

import org.testng.IAnnotationTransformer2;
import org.testng.annotations.IConfigurationAnnotation;
import org.testng.annotations.IDataProviderAnnotation;
import org.testng.annotations.IFactoryAnnotation;
import org.testng.annotations.ITestAnnotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Transform2 implements IAnnotationTransformer2 {

    public void transform(IConfigurationAnnotation iConfigurationAnnotation, Class aClass, Constructor constructor, Method method) {
    }

    public void transform(IDataProviderAnnotation iDataProviderAnnotation, Method method) {
        if (iDataProviderAnnotation.getName().equals("tom"))  //匹配名为data的DataProvider
            iDataProviderAnnotation.setParallel(true); //设置并行
    }

    public void transform(IFactoryAnnotation iFactoryAnnotation, Method method) {
        iFactoryAnnotation.setDataProvider("data"); 
    }

    public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method) {
    }
}

编写testng.xml(不使用监听器)如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" >
    <test verbose="2" preserve-order="true" name="Test">
        <classes>
            <class name="TransformFactory">
            </class>
        </classes>
    </test>
</suite>

执行结果如下:

Test annotationTransformer!
DataProviderName:tom

===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

修改testng.xml(使用监听器)如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" >
    <listeners>
        <listener class-name="Transform2"/>
    </listeners>

    <test verbose="2" preserve-order="true" name="Test">
        <classes>
            <class name="TransformFactory">
            </class>
        </classes>
    </test>
</suite>

执行结果如下:

Test annotationTransformer!
DataProviderName:data

===============================================
All Test Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

对比以上两个执行结果,可发现@Factory的dataProvider确实被修改了。

注意事项

@listener注解不能包含IAnnotationTransformer和IAnnotationTransformer2的实现类,原因是这两种监听器必须在更早的阶段添加到 TestNG 中才能实施修改注释的操作,所以它们只能在 testng.xml 添加。还是最前面的例子,通过以下方式添加监听器不会起作用。

@Listeners(Transform.class)
public class TestTransform {
    @Test
    public void test(){
        System.out.println("Test annotationTransformer!");
    }
}

2.3 IHookable

IHookable接口继承自ITestNGListener接口,其定义了唯一的run方法。IHookable 监听器提供了类似与面向切面编程(AOP)中的 Around Advice 的功能。它在测试方法执行前后提供了切入点,从而使用户能够在测试方法运行前后注入特定的功能。例如,用户可以在当前测试方法运行前加入特定的验证逻辑以决定测试方法是否运行或者跳过,甚至覆盖测试方法的逻辑。下面是 IHookable 监听器要求实现的方法签名。

public interface IHookable extends ITestNGListener {
    void run(IHookCallBack var1, ITestResult var2);
}

如要运行原始测试方法逻辑,需要调用 runTestMethod 方法。

callBack.runTestMethod(testResult);

典型应用是执行测试方法前进行授权检查,根据授权结果执行测试,官网举例如下:

public class MyHook implements IHookable {
  public void run(final IHookCallBack icb, ITestResult testResult) {
    // Preferably initialized in a @Configuration method
    mySubject = authenticateWithJAAs();
    
    Subject.doAs(mySubject, new PrivilegedExceptionAction() {
      public Object run() {
        icb.callback(testResult);
      }
    };
  }
}

示例1

一个简单的例子如下。
编写IHookable的实现类,简单输出“tom”。

import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;

public class IHookableImp implements IHookable {
    @Override
    public void run(IHookCallBack iHookCallBack, ITestResult iTestResult) {
        System.out.println("tom");
        iHookCallBack.runTestMethod(iTestResult);
    }
}

编写测试类如下:

import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(IHookableImp.class)
public class IHookableImpTest {
    @Test
    public void test(){
        System.out.println("test");
    }
}

执行结果:

tom
test

===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

示例2

IHookable实现类

public class IHookableImp implements IHookable {
    @Override
    public void run(IHookCallBack iHookCallBack, ITestResult iTestResult) {
        ConstructorOrMethod method = iTestResult.getMethod().getConstructorOrMethod();
        String name = method.getName();
        System.out.println("测试method是 "+name);
        System.out.println("开始执行~");
        //测试用例开始执行
        iHookCallBack.runTestMethod(iTestResult); 
        System.out.println("结束~");
    }
}

测试用例

@Listeners(IHookableImp.class)
public class Test_case {
    @Test
    public void case1(){
        System.out.println("case1方法");
    }
}

执行结果:

2.4 IInvokedMethodListener

与 IHookable 类似,IInvokedMethodListener 提供了类似与面向切面编程(AOP)中的 Before Advice 和 After Advice 的功能。IInvokedMethodListener接口继承自ITestNGListener接口,其定义了beforeInvocation和afterInvocation方法。TestNG在调用方法前、后启用该监听器,常用于日志的采集。

public interface IInvokedMethodListener extends ITestNGListener {
    void beforeInvocation(IInvokedMethod var1, ITestResult var2);

    void afterInvocation(IInvokedMethod var1, ITestResult var2);
}

示例1

编写IInvokedMethodListenerImp实现类:

import org.testng.*;

public class IInvokedMethodListenerImp implements IInvokedMethodListener {

    @Override
    public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        System.out.println("beforeInvocation:"+iTestResult.getName());
    }

    @Override
    public void afterInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        System.out.println("afterInvocation:"+iTestResult.getName());
    }
}

编写测试类:

import org.testng.annotations.BeforeClass;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(IInvokedMethodListenerImp.class)
public class IInvokedMethodListenerImpTest {

    @BeforeClass
    public void bfClass(){
        System.out.println("bfClass123");
    }

    @Test
    public void test(){
        System.out.println("test123");
    }
}

执行结果如下:

beforeInvocation:bfClass
bfClass123
afterInvocation:bfClass
beforeInvocation:test
test123
afterInvocation:test

===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

如上结果所示,所有被注解的方法执行前都先执行监听器的beforeInvocation方法,执行后都执行afterInvocation方法。
另外,TestNG还提供了IInvokedMethodListener2监听器,其中定义的方法加入用户信息的参数,使用起来更灵活。

public interface IInvokedMethodListener2 extends IInvokedMethodListener {
    void beforeInvocation(IInvokedMethod var1, ITestResult var2, ITestContext var3);

    void afterInvocation(IInvokedMethod var1, ITestResult var2, ITestContext var3);
}

示例2

IInvokedMethodListener实现类

public class IInvokedMethodListenerImp implements IInvokedMethodListener {
    //TestNG在调用方法前、后启用该监听器,常用于日志的采集。
    @Override
    public void afterInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        // TODO Auto-generated method stub
        //获取执行的@Test方法
        System.out.println(iTestResult.getName());
    }
    @Override
    public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
        // TODO Auto-generated method stub
        //获取执行的@Test方法
        System.out.println(iTestResult.getName());
    }
}

测试用例

@Listeners(IInvokedMethodListenerImp.class)
public class Test_case {
    @Test
    public void case1(){
        System.out.println("执行了case1测试方法");
    }
}

测试结果

2.5 IMethodInterceptor

TestNG 启动之后,第一件要做的事情是将所有的测试方法分成两类:一类是顺序运行的测试方法;一类是没有特定运行顺序的测试方法。

TestNG 通过 @Test 注解中的 dependsOnGroups 和 dependsOnMethods 使用户能够定义测试方法之间的依赖关系。这种依赖关系也就决定这些测试方法必须按着怎样的顺序运行,这就是第一类。除此以外的便是第二类。对于第二类中的测试方法,尽管默认 TestNG 会尝试用类名将它们分组,但是理论上,它们的运行顺序是随机的,甚至每次运行的顺序都可能不同。因此为了使用户拥有对第二类测试方法更大的控制权,IMethodInterceptor 监听器产生了。用户要实现的方法如下。

public interface IMethodInterceptor {
  List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context);
}

intercept 方法在所有测试方法被分类后以及所有测试方法被执行前被调用。所有的测试方法将按照 intercept 返回值列表中的顺序被执行。因此,用户在 intercept 方法中可以对列表进行修改,比如重新排序,甚至增加或者减少测试方法。另外,方法中传递的ITestContext入参,用户可以自定义某些值(IAttributes.setAttribute(String, Object)
),后续在测试报告展现。

示例

编写IMethodInterceptor实现类,用于执行grp1组的测试用例。

import com.Test;
import org.testng.IMethodInstance;
import org.testng.IMethodInterceptor;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MethodInterceptors implements IMethodInterceptor {

    public List<IMethodInstance> intercept(List<IMethodInstance> methodInstances, ITestContext context) {
        List<IMethodInstance> result = new ArrayList<IMethodInstance>();

        for (IMethodInstance methodInstance : methodInstances) {
            ITestNGMethod method = methodInstance.getMethod();
            Set<String> groups = new HashSet<String>();

            if (method.isTest()) { //如果是@Test注解
                for (String group : method.getGroups()) {
                    groups.add(group);  //获取@Test注解的所有组
                }
            }

            if (groups.contains("grp1"))  //只运行grp1组
                result.add(methodInstance);
        }

        return result;
    }
}

编写测试类,并添加监听器注解@Listeners(MethodInterceptors.class):

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(MethodInterceptors.class)
public class MethodInterceptorsTest {
    @BeforeClass
    public void bfClass(){
        System.out.println("BeforeClass");
    }

    @Test(groups = "grp1")
    public void test1(){
        System.out.println("test1");
    }

    @Test(groups = "grp2")
    public void test2(){
        System.out.println("test2");
    }

    @Test(groups = "grp2")
    public void test3(){
        System.out.println("test3");
    }

    @AfterClass
    public void afClass(){
        System.out.println("AfterClass");
    }
}

执行结果如下:

BeforeClass
test1
AfterClass

===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

如上执行结果所示,MethodInterceptors监听器确实起作用了。

2.6 IReporter

TestNG 提供了默认的测试报表。但如果用户希望有不同格式的测试报表,就需要使用 IReporter 监听器。IReporter 监听器只有一个方法需要实现。

public interface IReporter extends ITestNGListener {
    void generateReport(List<XmlSuite> var1, List<ISuite> var2, String outputDirectory);
}

IReporter接口继承自ITestNGListener接口,其定义了generateReport方法。TestNG在运行所有套件时都将调用此方法,通过遍历 xmlSuites 和 suites 能够获取所有测试方法的信息以及测试结果,后续可用于自定义测试报告。outputDirectory 是默认的测试报表生成路径,当然你可以指定其他路径生成报表。

示例1

编写IReporter实现类:

import org.testng.*;
import org.testng.xml.XmlSuite;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class IReporterImp implements IReporter {
    @Override
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> iSuites, String outputDirectory) {

        ArrayList<String> list = new ArrayList<String>();
        for(ISuite iSuite:iSuites){
            Map<String,ISuiteResult> iSuiteResultMap = iSuite.getResults();
            System.out.println("所有执行的方法:"+iSuite.getAllInvokedMethods());
            System.out.println("获取所有@Test标注的方法:"+iSuite.getAllMethods());
            System.out.println("suiteName:"+iSuite.getName());
            System.out.println("输出路径:"+iSuite.getOutputDirectory());
            System.out.println("并发方式:"+iSuite.getParallel());
            System.out.println("参数tom的值:"+iSuite.getParameter("tom"));
            System.out.println("报告路径:"+outputDirectory);

            for(ISuiteResult iSuiteResult:iSuiteResultMap.values()){
                ITestContext iTestContext = iSuiteResult.getTestContext();
                IResultMap iResultMap = iTestContext.getPassedTests();
                IResultMap iResultMap1 = iTestContext.getFailedTests();

                Set<ITestResult> iTestResultset = iResultMap.getAllResults();
                for(ITestResult iTestResult:iTestResultset){
                    System.out.println("测试方法:"+iTestResult.getName());
                    System.out.println("执行结果(1-成功,2-失败,3-skip):"+iTestResult.getStatus());
                    System.out.println("开始时间:"+iTestResult.getStartMillis());
                    System.out.println("结束时间:"+iTestResult.getEndMillis());
                }

                Set<ITestResult> iTestResultset1 = iResultMap1.getAllResults();
                for(ITestResult iTestResult1:iTestResultset1){
                    System.out.println("测试方法:"+iTestResult1.getName());
                    System.out.println("执行结果(1-成功,2-失败,3-skip):"+iTestResult1.getStatus());
                    System.out.println("开始时间:"+iTestResult1.getStartMillis());
                    System.out.println("结束时间:"+iTestResult1.getEndMillis());
                }
            }
        }
    }
}

编写测试类:

import org.testng.Assert;
import org.testng.annotations.*;

@Test(groups = "test1")
public class TestNGHelloWorld1 {
    @BeforeTest
    public void bfTest() {
        System.out.println("TestNGHelloWorld1 beforTest!");
    }

    @Test(expectedExceptions = ArithmeticException.class, expectedExceptionsMessageRegExp = ".*zero")
    public void helloWorldTest1() {
        System.out.println("TestNGHelloWorld1 Test1!");
        int c = 1 / 0;
        Assert.assertEquals("1", "1");
    }

    @Test()
    @Parameters(value = "para")
    public void helloWorldTest2(@Optional("Tom")String str) {
        Assert.assertEquals("1", "2");
        System.out.println("TestNGHelloWorld1 Test2! "+ str);
    }

    @AfterTest
    public void AfTest() {
        System.out.println("TestNGHelloWorld1 AfterTest!");
    }
}

配置testng.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" parallel="classes">
    <listeners>
        <listener class-name="IReporterImp"/>
    </listeners>

    <parameter name="tom" value="Tomandy"/>

    <test verbose="2" preserve-order="true" name="Test">
        <classes>
            <class name="TestNGHelloWorld1">
            </class>
        </classes>
    </test>
</suite>

执行结果如下:

TestNGHelloWorld1 beforTest!
TestNGHelloWorld1 Test1!

java.lang.AssertionError: expected [2] but found [1]
Expected :2
Actual   :1
 <Click to see difference>


    at org.testng.Assert.fail(Assert.java:93)
    at org.testng.Assert.failNotEquals(Assert.java:512)
    at org.testng.Assert.assertEqualsImpl(Assert.java:134)
    at org.testng.Assert.assertEquals(Assert.java:115)
    at org.testng.Assert.assertEquals(Assert.java:189)
    at org.testng.Assert.assertEquals(Assert.java:199)
    at TestNGHelloWorld1.helloWorldTest2(TestNGHelloWorld1.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:108)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:661)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:869)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1193)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:126)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

TestNGHelloWorld1 AfterTest!

===============================================
All Test Suite
Total tests run: 2, Failures: 1, Skips: 0
===============================================

所有执行的方法:[TestNGHelloWorld1.bfTest()[pri:0, instance:TestNGHelloWorld1@15f2bb7] 23014327, TestNGHelloWorld1.helloWorldTest1()[pri:0, instance:TestNGHelloWorld1@15f2bb7] 23014327, TestNGHelloWorld1.helloWorldTest2(java.lang.String)[pri:0, instance:TestNGHelloWorld1@15f2bb7]Tom  23014327, TestNGHelloWorld1.AfTest()[pri:0, instance:TestNGHelloWorld1@15f2bb7] 23014327]
获取所有@Test标注的方法:[TestNGHelloWorld1.helloWorldTest1()[pri:0, instance:TestNGHelloWorld1@15f2bb7], TestNGHelloWorld1.helloWorldTest2(java.lang.String)[pri:0, instance:TestNGHelloWorld1@15f2bb7]]
suiteName:All Test Suite
输出路径:D:\IntelliJ_IDEA_workspace\TestNG\test-output\All Test Suite
并发方式:classes
参数tom的值:Tomandy
报告路径:test-output
测试方法:helloWorldTest1
执行结果(1-成功,2-失败,3-skip):1
开始时间:1536288995670
结束时间:1536288995670
测试方法:helloWorldTest2
执行结果(1-成功,2-失败,3-skip):2
开始时间:1536288995675
结束时间:1536288995679

示例2

IReporter实现类

public class IReporterImp implements IReporter {
    //通过遍历 xmlSuites 和 suites 能够获取所有测试方法的信息以及测试结果,后续可用于自定义测试报告。
    @Override
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> iSuites, String outputDirectory) {
        
        for(ISuite iSuite:iSuites){
            Map<String,ISuiteResult> iSuiteResultMap = iSuite.getResults();
            //获取所有执行的方法
            System.out.println("所有执行的方法:"+iSuite.getAllInvokedMethods());
            //获取所有@Test标注的方法
            System.out.println("获取所有@Test标注的方法:"+iSuite.getAllMethods());
            //获取suite标签的name属性
            System.out.println("suiteName:"+iSuite.getName());
            //获取测试报告的输出路径
            System.out.println("输出路径:"+iSuite.getOutputDirectory());
            System.out.println("报告路径:"+outputDirectory);
            //获取并发方式
            System.out.println("并发方式:"+iSuite.getParallel());
            
            for(ISuiteResult iSuiteResult:iSuiteResultMap.values()){
                ITestContext iTestContext = iSuiteResult.getTestContext();
                IResultMap iResultMap = iTestContext.getPassedTests();
                
                Set<ITestResult> iTestResultset = iResultMap.getAllResults();
                for(ITestResult iTestResult:iTestResultset){
                    //获取执行的Test方法
                    System.out.println("测试方法:"+iTestResult.getName());
                    //获取执行结果
                    System.out.println("执行结果(1-成功,2-失败,3-skip):"+iTestResult.getStatus());
                    //获取开始执行的时间
                    System.out.println("开始时间:"+iTestResult.getStartMillis());
                    //获取结束执行的时间
                    System.out.println("结束时间:"+iTestResult.getEndMillis());
                }
                
            }
            
        }
        
    }

}

测试用例

@Listeners(IReporterImp.class)
public class Test_case {
    @Test
    public void case1(){
        System.out.println("执行了case1测试方法");
    }
}

运行结果:

2.7 ISuiteListener

ISuiteListener接口继承自ITestNGListener接口。ISuiteListener 类似于 IInvokedMethodListener,区别是 IInvokedMethodListener 针对的是测试方法,而 ISuiteListener 针对的是测试套件。ISuiteListener 使用户有机会在测试套件开始执行以及执行结束之后嵌入自己的逻辑。该监听器要求实现的方法如下。

public interface ISuiteListener extends ITestNGListener {
    void onStart(ISuite var1);

    void onFinish(ISuite var1);
}

2.8 ITestListener

如果要在测试方法执行成功、失败或者跳过时指定不同后续行为,可以通过 IInvokedMethodListener 实现,不过更为简便的方式是利用 ITestListener 监听器。ITestListener 接口继承自ITestNGListener接口,ITestListener 监听器要求实现的方法中包含如下三个。

public interface ITestListener extends ITestNGListener {
   //每次调用测试之前都会调用
    void onTestStart(ITestResult var1);
   //每次测试成功时调用。
    void onTestSuccess(ITestResult var1);
   //每次测试失败时调用。
    void onTestFailure(ITestResult var1);
   //每次测试跳过时调用。
    void onTestSkipped(ITestResult var1);
   //执行测试失败且 successPercentage属性满足条件是调用
    void onTestFailedButWithinSuccessPercentage(ITestResult var1);
   //在实例化测试类之后和在调用任何配置方法之前调用。
    void onStart(ITestContext var1);
   //在运行所有测试并调用所有配置方法之后调用。
    void onFinish(ITestContext var1);
}

除了以上三个方法,ITestListener 还声明了其他一些方法,大家可以自行查阅 TestNG Javadoc 了解细节。

另外,TestListenerAdapter 已经实现 ITestListener,并且提供了一些有用的方法,比如分别获取所有成功失败跳过三种测试结果的测试方法的方法,并且 ITestListner 中有很多方法而 TestListenerAdapter 已给出了默认实现。因此,在实际应用过程中我们只需继承 TestListenerAdapter 后,只关注需要修改的方法。

示例1

编写TestListenerAdapter子类,重写onTestFailure,onTestSkipped,onTestSuccess方法:

import org.testng.ITestResult;
import org.testng.TestListenerAdapter;

import static org.testng.Reporter.log;

public class TestListenerAdapterImp extends TestListenerAdapter {
    @Override
    public void onTestFailure(ITestResult tr) {
        System.out.println("Failure");
    }

    @Override
    public void onTestSkipped(ITestResult tr) {
        System.out.println("Skip");
    }

    @Override
    public void onTestSuccess(ITestResult tr) {
        System.out.println("Success");
    }

}

编写测试类:

import org.testng.Assert;
import org.testng.annotations.*;

@Test(groups = "test1")
public class TestNGHelloWorld1 {
    @BeforeTest
    public void bfTest() {
        System.out.println("TestNGHelloWorld1 beforTest!");
    }

    @Test(expectedExceptions = ArithmeticException.class, expectedExceptionsMessageRegExp = ".*zero")
    public void helloWorldTest1() {
        System.out.println("TestNGHelloWorld1 Test1!");
        int c = 1 / 0;
        Assert.assertEquals("1", "1");
    }

    @Test()
    @Parameters(value = "para")
    public void helloWorldTest2(@Optional("Tom")String str) {
        Assert.assertEquals("1", "2");
        System.out.println("TestNGHelloWorld1 Test2! "+ str);
    }

    @AfterTest
    public void AfTest() {
        System.out.println("TestNGHelloWorld1 AfterTest!");
    }
}

配置testng.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" parallel="classes">
    <listeners>
        <listener class-name="TestListenerAdapterImp"/>
    </listeners>

    <parameter name="tom" value="Tomandy"/>

    <test verbose="2" preserve-order="true" name="Test">
        <classes>
            <class name="TestNGHelloWorld1">
            </class>
        </classes>
    </test>
</suite>

执行结果如下:

TestNGHelloWorld1 beforTest!
TestNGHelloWorld1 Test1!
Success

java.lang.AssertionError: expected [2] but found [1]
Expected :2
Actual   :1
 <Click to see difference>


    at org.testng.Assert.fail(Assert.java:93)
    at org.testng.Assert.failNotEquals(Assert.java:512)
    at org.testng.Assert.assertEqualsImpl(Assert.java:134)
    at org.testng.Assert.assertEquals(Assert.java:115)
    at org.testng.Assert.assertEquals(Assert.java:189)
    at org.testng.Assert.assertEquals(Assert.java:199)
    at TestNGHelloWorld1.helloWorldTest2(TestNGHelloWorld1.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:108)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:661)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:869)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1193)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:126)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Failure
TestNGHelloWorld1 AfterTest!

===============================================
All Test Suite
Total tests run: 2, Failures: 1, Skips: 0
===============================================

如上执行结果所示,每次执行失败或成功都能被监听器捕获。

示例2

Testlisteneradapter实现类

public class TestListenerAdapterImp extends TestListenerAdapter {
    private int m_count = 0;
    
    @Override
    public void onTestFailure(ITestResult tr) {
        log(tr.getName()+ "--Test method failed\n");
    }
     
    @Override
    public void onTestSkipped(ITestResult tr) {
        log(tr.getName()+ "--Test method skipped\n");
    }
     
    @Override
    public void onTestSuccess(ITestResult tr) {
        log(tr.getName()+ "--Test method success\n");
    }
     
    private void log(String string) {
        System.out.print(string);
        if (++m_count % 40 == 0) {
        System.out.println("");
        }
    }

}

测试方法

@Listeners(TestListenerAdapterImp.class)
public class Test_case {
    @Test
    public void case1(){
        System.out.println("执行了case1测试方法");
    }
}

运行结果

3. 监听器的使用方法

前文已讲过,监听器的编码过程就是定义一个 Java 类实现监听器接口。下面简单介绍一下监听器的几种使用方法。

3.1 在 testng.xml中使用TestNG监听器

TestNG 通过 testng.xml 配置所有的测试方法。Testng.xml 提供了 listeners 和 listener 标签用来添加自定义的监听器。下面示范的是本文示例代码中包含的 testng.xml 文件。

<suite name="TestNGSample">
    <listeners>
        <listener class-name="listeners.OSFilter" />
        <listener class-name="listeners.ProgressTracker" />
    </listeners>
    <test name="ProgressTracker Demo">
        <classes>
            <class name="tests.SampleTest" />
        </classes>
    </test>
</suite>

3.2 在源代码中使用TestNG监听器

通过 @Listeners 注解,可以直接在 Java 源代码中添加 TestNG 监听器。下面示范的是本文示例代码中如何使用 @Listeners 注解。

@Listeners({ OSFilter.class, ProgressTracker.class })
public class SampleTest {

    @Test(groups = { OSNames.OS_LINUX })
    public void test1() {
        sleep(5000);
        System.out.println(">>>test1");
    }

值得注意的是:

  • 在 @Listeners 中添加监听器跟在 testng.xml 添加监听器一样,将被应用到整个测试套件中的测试方法。如果需要控制监听器的应用范围(比如添加的监听器仅使用于某些测试测试类或者某些测试方法),则必须在监听器类中编写适当的判断逻辑。

可以通过以下例子来验证。
编写TestListenerAdapter子类,重写onTestFailure,onTestSkipped,onTestSuccess方法:

import org.testng.ITestResult;
import org.testng.TestListenerAdapter;

import static org.testng.Reporter.log;

public class TestListenerAdapterImp extends TestListenerAdapter {
    @Override
    public void onTestFailure(ITestResult tr) {
        System.out.println("Failure");
    }

    @Override
    public void onTestSkipped(ITestResult tr) {
        System.out.println("Skip");
    }

    @Override
    public void onTestSuccess(ITestResult tr) {
        System.out.println("Success");
    }

}

编写测试类ListenerTest,为添加@Listeners注解:

import org.testng.annotations.Test;

public class ListenerTest {
    @Test
    public void Ltest(){
        System.out.println("ListenerTest @Test");
    }
}

编写测试类ListenerTest1,添加@Listeners注解:

import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestListenerAdapterImp.class)
public class ListenerTest1 {
    @Test
    public void Ltest1(){
        System.out.println("ListenerTest1 @Test");
    }

    @Test
    public void Ltest2(){
        Assert.assertEquals("1","2");
    }
}

配置testng.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" parallel="classes">

    <test verbose="2" preserve-order="true" name="Test">
        <classes>
            <class name="ListenerTest"/>
            <class name="ListenerTest1"/>
        </classes>
    </test>
</suite>

执行结果:

ListenerTest1 @Test
ListenerTest @Test
Success
Success
Failure

java.lang.AssertionError: expected [2] but found [1]
Expected :2
Actual   :1
 <Click to see difference>


    at org.testng.Assert.fail(Assert.java:93)
    at org.testng.Assert.failNotEquals(Assert.java:512)
    at org.testng.Assert.assertEqualsImpl(Assert.java:134)
    at org.testng.Assert.assertEquals(Assert.java:115)
    at org.testng.Assert.assertEquals(Assert.java:189)
    at org.testng.Assert.assertEquals(Assert.java:199)
    at ListenerTest1.Ltest2(ListenerTest1.java:14)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:108)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:661)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:869)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1193)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:126)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)


===============================================
All Test Suite
Total tests run: 3, Failures: 1, Skips: 0
===============================================

如果结果所示,ListenerTest类的测试方法也被监听器捕获,尽管该类未添加@Listeners注解,那么有没有方法来限制@Listeners的作用范围呢?答案是有,使用者可以在监听器类中编写判断逻辑实现,官网亦给出了相关的实现示例

  • 在 @Listeners 中添加监听器跟在 testng.xml 添加监听器的不同之处在于,它不能添加 IAnnotationTransformer 和 IAnnotationTransformer2 监听器。原因是因为这两种监听器必须在更早的阶段添加到 TestNG 中才能实施修改注释的操作,所以它们只能在 testng.xml 添加。
  • TestNG 对添加的监听器不做去重判断。因此,如果 testng.xml 和源代码中添加了相同的监听器,该监听器的方法会被调用两次。有关这一点,大家可以通过运行本文附带的示例代码包中 testng.xml 验证。因此,切记,不要通过多种方式重复添加监听器。

3.3 通过ServiceLoader使用 TestNG监听器

Java SE 6 开始提供了 ServiceLoader。它可以帮助用户查找、加载和使用服务提供程序,从而在无需修改原有代码的情况下轻易地扩展目标应用程序。通过 ServiceLoader 的方式使用 TestNG 监听器,简单来说,就是创建一个 jar 文件,里面包含 TestNG 监听器的实现类以及 ServiceLoader 需要的配置信息,并在运行 TestNG 时把该 jar 文件加载到类路径中。具体步骤请查阅 TestNG 官方文档。这样做的好处是:

  1. 可以轻松地与其他人分享 TestNG 监听器。
  2. 当有很多 testng.xml 文件时,不需要重复把监听器添加到每个文件中。

3.4 通过命令行使用TestNG监听器

通过命令行使用 TestNG 监听器,需要在命令行中加入 “-listener” 参数。如要指定多个监听器,用逗号分隔。下面是一个调用的示例。

java org.testng.TestNG -listener MyListener testng1.xml [testng2.xml testng3.xml ...]

3.5 通过 IDE 使用 TestNG 监听器

TestNG 在多种 IDE 中都有插件支持,比如 Eclipse 和 IDEA。因为最终 IDE 也是以命令行的方式调用 TestNG,因此在 IDE 中也是通过添加 “-listener” 参数使用 TestNG 监听器。下图以 Eclipse 为例示范了 TestNG 监听器的配置方法。

图 1. Eclipse 中 TestNG 监听器的配置

图 1. Eclipse 中 TestNG 监听器的配置

除此之外,ANT 跟 Maven 也都有相应 Task 和插件运行 TestNG 测试,按照相应的文档配置监听器即可,这里便不一一赘述。

4. 示例

本文提供了两个示范的监听器的实现,它们分别实现了动态测试方法过滤和测试进度跟踪功能。

4.1 动态测试方法过滤监听器(listeners.OSFilter)

TestNG 提供了分组特性,但它的局限是组名必须是静态的。假如,现在有一些测试方法,部分适用于 Linux 和 Windows,部分仅适用于其一。通过默认的 TestNG 分组特性,大概要定义两个 testng.xml 文件,指定不同的组名,并且在指定测试应用时要小心不要把配置与环境的对应弄错。

示例代码中的监听器采用的方法是在每个测试方法执行前,动态获取操作系统类型信息并将其与 @Test 注释中定义的操作系统比较以决定哪些测试方法应该运行。这样便省却了上述配置的麻烦。

如果仅仅为了跳过不合适的测试方法,也可以选用 IInvokedMethodListener 监听器。但因为该监听器还要统计所有传入的测试方法以及被忽略的测试方法的数目,所以选用了 IMethodInterceptor。值得注意的是,在 TestNG 的生命周期中,IMethodInterceptor 监听器的 intercept 方法事实上会被调用两次。为了避免代码被重复执行,本示例代码将返回的测试方法列表定义为成员变量,并通过判断该成员变量是否为 null 决定是否执行过滤逻辑。

4.2 测试进度跟踪监听器(listeners.ProgressTracker)

自动化单元测试和 API 测试通常运行比较快,但是 UI 测试运行较慢。对于长时间运行的测试,我们常常想要知道当前正在运行的测试方法名称以及预计剩余执行时间。这便是该监听器实现的功能。

由于需要通过统计每个测试方法的运行时间来预估剩余执行时间,该监听器选用了 IInvokedMethodListener。另外,预估剩余执行时间还需要知道整个测试套件要执行的测试方法的数目,单单使用 IInvokedMethodListener 无法得到该信息,因此,该监听器通过 listeners.OSFilter 计算全部要执行的测试方法的数目。预估的算法是根据已经使用的时间和执行的测试方法数量计算出每个测试方法的平均执行时间,然后用该平均时间乘以未执行的测试方法数目,从而得出预估剩余时间。该算法的问题在于,当每个测试方法执行时间差异较大并且测试方法数目较少时,该方法春在较大的误差,因此该时间只能作为参考。

4.3 如何运行示例代码

示例代码是一个 Eclipse 项目导出的压缩文件,因此只要在 Eclipse 中导入该文件并安装 TestNG 的 Eclipse 插件即可运行。

tests.SampleTest 是一个示范的 TestNG 测试类,该类中定义了 5 个测试方法:一个指定为仅运行在 Linux,两个指定为运行在 Linux 和 Windows,另外两个指定为仅运行在 Windows。为简单期间,测试方法体仅用 Thread.sleep()模拟。该类中已添加了 @Listeners 注解,将该类作为 TestNG 测试运行,将得到如下的正常输出(此为 Linux 下的输出结果)。

[TestNG] Running:
 /tmp/testng-eclipse-814456884/testng-customsuite.xml

Ignored Test Methods: [test4(tests.SampleTest), test5(tests.SampleTest)]
[Begin] test1(SampleTest)
>>>test1
[Progress]33% (1/3) , Elapsed:00:00:05, Estimated Time Remaining:00:00:10
[End] test1(SampleTest): Passed

[Begin] test2(SampleTest)
>>>test2
[Progress]67% (2/3) , Elapsed:00:00:08, Estimated Time Remaining:00:00:04
[End] test2(SampleTest): Passed

[Begin] test3(SampleTest)
>>>test3
[Progress]100% (3/3) , Elapsed:00:00:10, Estimated Time Remaining:00:00:00
[End] test3(SampleTest): Passed

PASSED: test1
PASSED: test2
PASSED: test3

===============================================
 Default test
 Tests run: 3, Failures: 0, Skips: 0
===============================================

===============================================
Default suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================

[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@5a31442c: 12 ms
[TestNG] Time taken by org.testng.reporters.EmailableReporter@64a0401: 5 ms
[TestNG] Time taken by org.testng.reporters.XMLReporter@a98e9456: 20 ms
[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@276fc2ae: 77 ms
[TestNG] Time taken by [FailedReporter passed=0 failed=0 skipped=0]: 1 ms
[TestNG] Time taken by org.testng.reporters.jq.Main@6517ab3c: 63 ms

示例代码包中还包含有一个 testng.xml 文件。testng.xml 中也添加了监听器,因此运行 testng.xml 将看到重复的测试进度信息输出。需删除 tests.SampleTest 中 @Listeners 注解才能使 testng.xml 正常运行测试。

4.4 下载代码示例

TestNGSample.zip

 

5. 结束语

通过本文的介绍,大家可以了解到,TestNG 提供的多种监听器接口使 TestNG 具备强大的扩展性。选用什么监听器接口需根据实际需求而定。在动手开发自己的 TestNG 监听器之前,不妨先搜索一下互联网,兴许其他用户已经开发了类似的功能。例如,ReportNG 就是一款较为成熟的用于增强 TestNG 报表功能的插件。

 

 

 

 

 

 

 

参考文章:

1.实战 TestNG 监听器

2.TestNG系列(四)TestNG监听器

3.(十二)TestNG学习之路—注解转换器

4.(十三)TestNG学习之路—方法拦截器

5.(十四)TestNG学习之路—TestNG监听器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慕城南风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值