1. 程式人生 > >【TestNG】TestNG自定義監聽器大全

【TestNG】TestNG自定義監聽器大全

前言

TestNG預設有提供監聽器,但是如果我們想自定義監聽器也是可以的,自定義監聽器包括好幾種,部分如下所示:

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

全部監聽器如下圖所示:
監聽器all
使用命令列呼叫TestNG同時新增監聽器可以參考如下程式碼:

java org.testng.TestNG -listener MyTransformer testng.xml

下面介紹下這些:

一、ISuiteLinstener監聽器

這個其實不常用,只有兩個方法要實現,開始測試套和結束測試套。
實現自定義測試套監聽器可以這麼寫:

package com.demo.test.testng;

import org.testng.ISuite;
import org.testng.ISuiteListener;

public class MySuiteListener implements ISuiteListener{

	public void onStart(ISuite suite) {
		System.out.println("suite onStart!"
); } public void onFinish(ISuite suite) { System.out.println("suite onFinish!"); } }

測試用例如下:

package com.demo.test.testng;

import org.testng.annotations.Test;

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

對應的xml如下,注意裡邊的那個listener標籤,即為上面新增的那個監聽器:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="this suite" parallel="none" verbose="1">
  <listeners>
    <listener class-name="com.demo.test.testng.MySuiteListener" />
  </listeners>
  <test name="Test">
    <classes>
      <class name="com.demo.test.testng.ListenerTest"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

測試結果如下:

[RemoteTestNG] detected TestNG version 6.10.0
[TestNG] Running:
  D:\software\workspace\testng\src\main\java\com\demo\test\testCase\listenerTest1.xml

suite onStart!
test1
test2
suite onFinish!

===============================================
this suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

實現這個介面的自定義監聽器只能通過xml裡配置listener來加入;

二、IAlterSuiteListener監聽器

這個監聽器可以用於獲取xml檔案對應的XmlSuite,對於我們想根據suite修改一些引數啊之類的東西的需求,可以通過實現此監聽器來實現,方法如下:


public class MyAlterSuiteNameListener implements IAlterSuiteListener {
 
    @Override
    public void alter(List<XmlSuite> suites) {
        XmlSuite suite = suites.get(0);//只取第一個suite
        suite.setName(getClass().getSimpleName());
    }

在獲取到XmlSuite物件後就可以操作裡邊的所有內容了,包括配置和測試用例等;
注意:IAlterSuiteListener監聽器的使用有特殊要求。使用IAlterSuiteListener監聽器,只能通過在testng.xml中的,或者通過Java的SPI介面。除此之外的其他方法,如在測試類中通過@Listeners標註等,都是無效的。

三、ITestListener監聽器

這個監聽器比較常用了,內含的介面比較多,在我們需要對失敗等用例做一些特別操作的時候就需要這個了。多適用於這樣一些情況:
當使用testng執行測試時,我們常會想在某個階段做一些特別的處理,比如:測試成功結束後,測試失敗後,跳過某個指令碼後,全部指令碼執行完畢後。
要想達成這個目標,我們需要實現testng的ITestListener介面,自定義一個自己的listener。
ITestListener包含兩種型別的方法:

  • 一類是測試用例級別的,例如onTestStart,onTestSuccess,onTestFailure,onTestSkipped,onTestFailedButWithinSuccessPercentage;
  • 另一類是測試集級別的,例如onStart,onFinish。這些方法有一個輸入引數,result或者context。result是ITestResult型別的,可以知曉測試用例成功或者失敗和測試何時開始等資訊。context是ITestContext型別的,適用於測試集級別,可以知曉成功的指令碼有哪些,失敗的指令碼有哪些。
    範例如下:
    監聽器如下:
package com.demo.test.testng;

import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;

public class MyTestListener implements ITestListener{

	public void onTestStart(ITestResult result) {
		System.out.println("onTestStart");
	}

	public void onTestSuccess(ITestResult result) {
		System.out.println("onTestSuccess");
	}

	public void onTestFailure(ITestResult result) {
		System.out.println("onTestFailure");
	}

	public void onTestSkipped(ITestResult result) {
		System.out.println("onTestSkipped");
	}

	public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
		System.out.println("onTestFailedButWithinSuccessPercentage");
	}

	public void onStart(ITestContext context) {
		System.out.println("onStart");
	}

	public void onFinish(ITestContext context) {
		System.out.println("onFinish");
	}

}

測試用例:

package com.demo.test.testng;

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

@Listeners(MyTestListener.class)
public class ListenerTest {
	
  @Test
  public void test1() {
	  System.out.println("test1");
  }
  
  @Test
  public void test2() {
	  System.out.println("test2");
	  Assert.assertFalse(true);
  }
}

用例執行結果如下:

[RemoteTestNG] detected TestNG version 6.10.0
[TestNG] Running:
  C:\Users\dufei\AppData\Local\Temp\testng-eclipse-719761139\testng-customsuite.xml

onStart
onTestStart
test1
onTestSuccess
onTestStart
test2
onTestFailure
onFinish
PASSED: test1
FAILED: test2
java.lang.AssertionError: expected [false] but found [true]
	at org.testng.Assert.fail(Assert.java:94)
	at org.testng.Assert.failNotEquals(Assert.java:513)
	at org.testng.Assert.assertFalse(Assert.java:63)
	at org.testng.Assert.assertFalse(Assert.java:73)
	at com.demo.test.testng.ListenerTest.test2(ListenerTest.java:18)
	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:104)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:645)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:851)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1177)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
	at org.testng.TestRunner.privateRun(TestRunner.java:756)
	at org.testng.TestRunner.run(TestRunner.java:610)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:387)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:382)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:340)
	at org.testng.SuiteRunner.run(SuiteRunner.java:289)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1293)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1218)
	at org.testng.TestNG.runSuites(TestNG.java:1133)
	at org.testng.TestNG.run(TestNG.java:1104)
	at org.testng.remote.AbstractRemoteTestNG.run(AbstractRemoteTestNG.java:114)
	at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:251)
	at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:77)


===============================================
    Default test
    Tests run: 2, Failures: 1, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 2, Failures: 1, Skips: 0
===============================================

[TestNG] Time taken by [email protected]: 62 ms

當然也可以在xml中指定,如下xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="this suite" parallel="none" verbose="1">
  <listeners>
    <listener class-name="com.demo.test.testng.MySuiteListener" />
    <listener class-name="com.demo.test.testng.MyTestListener" />
  </listeners>
  <test name="Test">
    <classes>
      <class name="com.demo.test.testng.ListenerTest"/>
    </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

執行結果就不貼了,跟上面的那個結果類似。

四、IReporter監聽器

這個監聽器就是用於自定義報告的,這裡我們就可以把結果寫入資料庫、或者寫入xml、html或者EXCEL之類的。

package com.demo.test.testng;

import java.util.List;

import org.testng.IReporter;
import org.testng.ISuite;
import org.testng.xml.XmlSuite;

public class MyReporter implements IReporter{

	public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, 
			String outputDirectory) {
		
	}

}

範例就不寫了,這個根據實際需求來做即可;

五、IMethodInterceptor監聽器

此監聽器用於改變執行順序等,為一個方法級的監聽器,實現自定義監聽器的範例如下。
在TestNG的執行過程中,根據測試方法的執行順序,可以將測試方法分為如下兩類:

  • 順序執行的方法,即依賴於其他測試方法或者被其他測試方法所依賴
  • 執行無特定順序的方法,即測試方法的執行沒有特定的順序要求,不同的TestNG執行可能有不同的順序。

對於第一類測試方法,其執行順序不能隨意改變,必須符合依賴關係。但是對於第二類測試方法,TestNG提供了IMethodInterceptor監聽器,可以改變這些測試方法的執行順序。

監聽器程式碼:

package com.demo.test.testng;

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

import org.testng.IMethodInstance;
import org.testng.IMethodInterceptor;
import org.testng.ITestContext;
import org.testng.annotations.Test;


public class MyMethodInterceptor implements IMethodInterceptor
{

	public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) 
	{
		List<IMethodInstance> result = new ArrayList<IMethodInstance>();
		for (IMethodInstance m : methods) 
		{
			Test test = m.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class);
			Set<String> groups = new HashSet<String>();
			for (String group : test.groups()) 
			{
				groups.add(group);
			}
			if (groups.contains("dependGroup1")) 
			{
				result.add(0, m);
			}
			else {
				result.add(m);
			}
		}
		return result;

	}

}

用例程式碼,內含兩個group,正常執行順序是不能保證的:

package com.demo.test.testng;

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

public class DependTest1 {
	
	@Test(groups= {"dependGroup1"})
	public void dependTest1() 
	{
		System.out.println("dependTest1");
	}
	
	@Test(groups= {"dependGroup1"})
	public void dependTest2() 
	{
		System.out.println("dependTest2");
	}
	
	@Test(groups="dependGroup2")
	public void dependTest4() 
	{
		System.out.println("dependTest4");
		Assert.assertFalse(false);
	}
	
}

執行程式碼:

package com.demo.test.testng;

import org.testng.TestNG;

public class Entry {

	public static void main(String[] args) {
		TestNG testNG = new TestNG();
		testNG.setVerbose(3);
		testNG.setTestClasses(new Class[]{DependTest1.class});
		testNG.setMethodInterceptor(new MyMethodInterceptor());
		testNG.run();
	}
}

執行結果:

[TestRunner] Running the tests in 'Command line test' with parallel mode:none
[RunInfo] Adding method selector: [email protected] priority: 10
[TestClass] Creating TestClass for [ClassImpl class=com.demo.test.testng.DependTest1]
[TestNG] Running:
  Command line suite

[SuiteRunner] Created 1 TestRunners
[TestRunner] Running test Command line test on 1  classes,  included groups:[] excluded groups:[]
===== Test class
com.demo.test.testng.DependTest1
    @Test DependTest1.dependTest1()[pri:0, instance:[email protected]]
    @Test DependTest1.dependTest2()[pri:0, instance:[email protected]]
    @Test DependTest1.dependTest4()[pri:0, instance:[email protected]]
======
[Invoker 2065530879] Invoking com.demo.test.testng.DependTest1.dependTest2
dependTest2
[Invoker 2065530879] Invoking com.demo.test.testng.DependTest1.dependTest1
dependTest1
[Invoker 2065530879] Invoking com.demo.test.testng.DependTest1.dependTest4
dependTest4
===== Invoked methods
    DependTest1.dependTest2()[pri:0, instance:[email protected]] 1940030785
    DependTest1.dependTest1()[pri:0, instance:[email protected]] 1940030785
    DependTest1.dependTest4()[pri:0, instance:[email protected]] 1940030785
=====
[[Utils]] Attempting to create D:\software\workspace\testng\test-output\Command line suite\Command line test.xml
[[Utils]]   Directory D:\software\workspace\testng\test-output\Command line suite exists: true
Creating D:\software\workspace\testng\test-output\Command line suite\Command line test.xml
PASSED: dependTest2
PASSED: dependTest1
PASSED: dependTest4

===============================================
    Command line test
    Tests run: 3, Failures: 0, Skips: 0
===============================================

可以看到是group名稱為dependGroup1的group先執行了,與預期一致;

六、IInvokedMethodListener監聽器

在TestNG的執行過程中,通過IInvokedMethodListener監聽器,能夠在呼叫某個測試方法之前或者之後發出通知。

  • @Test方法
  • Configuration方法,即@Before方法和@After方法

目前,該監聽器只支援呼叫如下測試方法時發出通知:
此監聽器有兩個介面可以實現,引數稍有不同,如下所示:

package com.demo.test.testng;

import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;

public class MyIInvokedMethodListener1 implements IInvokedMethodListener{

	public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
		
	}

	public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
		// TODO Auto-generated method stub
		
	}

}

和:

package com.demo.test.testng;

import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener2;
import org.testng.ITestContext;
import org.testng.ITestResult;

public class MyIInvokedMethodListener2 implements IInvokedMethodListener2{

	public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
		// TODO Auto-generated method stub
		
	}

	public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
		// TODO Auto-generated method stub
		
	}

	public void beforeInvocation(IInvokedMethod method, ITestResult testResult, ITestContext context) {
		// TODO Auto-generated method stub
		
	}

	public void afterInvocation(IInvokedMethod method, ITestResult testResult, ITestContext context) {
		// TODO Auto-generated method stub
		
	}

}

七、IHookable監聽器

如果添加了這個監聽器,則此監聽器中的run方法被呼叫並替代所有被@Test註解標識的方法來執行,即TestNG在執行測試用例的時候是呼叫此監聽器的run方法的,run方法內部可以寫一些判斷來決定是否執行這個用例,譬如用例鑑權認證,例如在執行介面用例的時候實現此介面內部新增對於登入狀態的判定等;
原始碼提供了一個範例如下:

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

和@Configuration註解一起使用;

八、IAnnotationTransformer監聽器

到目前為止(TestNG 6.9.10),TestNG提供了三個IAnnotationTransformer系列的監聽器,這3個監聽器的目的都是在TestNG執行過程中動態改變測試類中Annotation的引數。其區別在於不同的IAnnotationTransformer監聽器,能夠操作的Annotation不同,具體情況如下:

  • IAnnotationTransformer,操作@Test標註
  • IAnnotationTransformer2,操作@Configuration標註、@DataProvider標註和@Factory標註
  • IAnnotationTransformer3,操作@Listeners標註
    三個介面實現如下:
package com.demo.test.testng;

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

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

public class MyIAnnotationTransformer1 implements IAnnotationTransformer{

	public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
		annotation.setAlwaysRun(true);
		testClass.isAnnotation();
		testConstructor.getName();
		testMethod.getName();
	}

}
package com.demo.test.testng;

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

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

public class MyIAnnotationTransformer2 implements IAnnotationTransformer2{

	public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
		// TODO Auto-generated method stub
		
	}

	public void transform(IConfigurationAnnotation annotation, Class testClass, Constructor testConstructor,
			Method testMethod) {
		// TODO Auto-generated method stub
		
	}

	public void transform(IDataProviderAnnotation annotation, Method method) {
		// TODO Auto-generated method stub
		
	}

	public void transform(IFactoryAnnotation annotation, Method method) {
		// TODO Auto-generated method stub
		
	}

}

package com.demo.test.testng;

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

import org.testng.IAnnotationTransformer3;
import org.testng.annotations.IConfigurationAnnotation;
import org.testng.annotations.IDataProviderAnnotation;
import org.testng