(十四)TestNG學習之路—TestNG監聽器

前言

前面的文章針對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 監聽器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章