前言
前面的文章針對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 文件時,不需要把監聽器添加到每個文件中。