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

目录

(一)TestNG学习之路—HelloWorld入门
(二)TestNG学习之路—注解及属性概览
(三)TestNG学习之路—TestNG.xml/YAML
(四)TestNG学习之路—注解详述之@Test
(五)TestNG学习之路—注解详述之参数化
(六)TestNG学习之路—注解详述之@Factory
(七)TestNG学习之路—注解详述之忽略测试
(八)TestNG学习之路—注解详述之并发
(九)TestNG学习之路—失败测试重跑
(十)TestNG学习之路—编码执行TestNG
(十一)TestNG学习之路—BeanShell高级用法
(十二)TestNG学习之路—注解转换器
(十三)TestNG学习之路—方法拦截器
(十四)TestNG学习之路—TestNG监听器
(十五)TestNG学习之路—依赖注入
(十六)TestNG学习之路—测试报告
(十七)基于TestNG+Rest Assured+Allure的接口自动化测试框架

前言

前面的文章针对IAnnotationTransformer,IAnnotationTransformer2,IMethodInterceptor几种监听器做了举例说明,该篇文章咱们接着再探讨几种常见的监听器,更多的监听器请访问javadoc

监听器

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

IHookable接口继承自ITestNGListener接口,其定义了唯一的run方法。如javadoc文档所述,它在测试方法执行前提供了切入点,根据当前执行的情况决定是否执行某个测试方法,典型应用是执行测试方法前进行授权检查,根据授权结果执行测试,官网举例如下:

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);
      }
    };
  }
}

一个简单的例子如下。
编写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
===============================================
IInvokedMethodListener
public interface IInvokedMethodListener extends ITestNGListener {
    void beforeInvocation(IInvokedMethod var1, ITestResult var2);

    void afterInvocation(IInvokedMethod var1, ITestResult var2);
}

IInvokedMethodListener接口继承自ITestNGListener接口,其定义了beforeInvocation和afterInvocation方法。TestNG在调用方法前、后启用该监听器,常用于日志的采集。举例如下:
编写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);
}
IReporter
public interface IReporter extends ITestNGListener {
    void generateReport(List<XmlSuite> var1, List<ISuite> var2, String outputDirectory);
}

IReporter接口继承自ITestNGListener接口,其定义了generateReport方法。TestNG在运行所有套件时都将调用此方法,通过遍历 xmlSuites 和 suites 能够获取所有测试方法的信息以及测试结果,后续可用于自定义测试报告,示例如下:
编写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
ISuiteListener
public interface ISuiteListener extends ITestNGListener {
    void onStart(ISuite var1);

    void onFinish(ISuite var1);
}

ISuiteListener接口继承自ITestNGListener接口,类似于 IInvokedMethodListener,用户可通过ISuiteListener监听器,在测试套件执行前或执行后嵌入相关逻辑。
由于跟IInvokedMethodListener类似,此处不再举例。

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 接口继承自ITestNGListener接口,定义的方法如上所示。但在实际应用过程中,我们一般使用TestListenerAdapter,因为ITestListner中的方法在TestListenerAdapter中给了默认实现,我们只需继承 TestListenerAdapter,重写自己感兴趣的方法即可,示例如下:
编写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
===============================================

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

@Listeners作用范围控制

类似testng.xml添加的listener一样,@Listeners注解作用于整个suite文件,可以通过以下例子来验证。
编写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的作用范围呢?答案是有,使用者可以在监听器类中编写判断逻辑实现,官网亦给出了相关的实现示例

监听器引用方式

命令行方式、ant、xml文件、@Listeners注解

以上几种方式前面的文章都已覆盖,不再详述。

ServiceLoader

JDK提供了一种非常优雅的机制,可以通过 ServiceLoader查找、加载和使用服务提供程序,从而在无需修改原有代码的情况下轻易地扩展目标应用程序(类似于Jmeter的jar扩展功能)。对于ServiceLoader,您所需要做的就是创建一个jar文件,其中包含侦听器和一些配置文件,在运行TestNG时将该jar文件放到classpath中,TestNG会自动找到它们,详细操作步骤参考官网。使用该种方式有以下好处。

  • 共享监听器。
  • 当有很多 testng.xml 文件时,不需要把监听器添加到每个文件中。

扩展学习资料

TestNg的IReporter接口的使用
实战 TestNG 监听器