TestNG 的 Listener 和 Reporter

最近忙着折腾 TestNG 项目,顺便整理记录一下。

记得几年前去某名企面试,我说我们在折腾平台,人家说平台有卵用,TestNG 就可以了。。。那么,TestNG 有什么优点呢?

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

TestNG 提供的几种监听器

  • IAnnotationTransformer 用来修改 @Test 注释(例annotation.setEnabled(false);)
  • IAnnotationTransformer2 在运行时修改除 @Test 以外的 TestNG 的注释 (@Configuration,@DataProvider 以及 @Factory 不过不推荐。建议使用 @BeforeSuite,@AfterSuite 代替)
  • IHookable 类似与面向方面编程(AOP)中的 Around Advice 的功能。它在测试方法执行前后提供了切入点。常用于测试方法执行前授权检查
  • IInvokedMethodListener 类似与面向方面编程(AOP)中的 Before Advice 和 After Advice,允许用户在当前测试方法被执行前和执行后注入特定的逻辑,比如,加日志。
  • IMethodInterceptor 可以用来重新排序、甚至增加、减少测试方法
  • IReporter 定制不同格式报表
  • ISuiteListener 在 Suite 之前、之前嵌入自定义逻辑
  • ITestListener 在测试方法执行成功、失败或者跳过时指定不同后续行为

监听器使用方法

testng.xml 中使用

1
2
3
4
5
<suite name="xxx">
<listeners>
<listener class-name="com.tracenote.XXXListener"/>
</listeners>
</suite>

源代码中使用

1
2
3
4
@Listeners({ XXXListener.class })
public class HiTest {
//...
}

@Listeners 中添加监听器跟在 testng.xml 添加监听器的不同之处在于,它不能添加 IAnnotationTransformer 和 IAnnotationTransformer2 监听器。原因是因为这两种监听器必须在更早的阶段添加到 TestNG 中才能实施修改注释的操作,所以它们只能在 testng.xml 添加。

通过 ServiceLoader 使用

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

通过命令行使用

加参数 -listener MyListener,XXXListener

监听器简单示例

IReporter

在测试方法执行后执行,通过遍历 xmlSuites 和 suites 能够获取所有测试方法的信息以及测试结果,后续可用于自定义测试报告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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());
}
}
}
}
}

IHookable

常用于测试方法执行前执行授权检查

1
2
3
4
5
6
7
8
9
10
11
12
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("结束~");
}
}

IInvokedMethodListener

常用于日志的采集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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());
}
}

TestListenerAdapter

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

用途举例,执行测试方法后执行,记录log信息,根据执行结果的不同调用不同的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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("");
}
}
}

Reporter

使用 org.testng.Reporter,可以实现直接把信息写入 HTML 报告。

1
Reporter.log("xxx");

当然,比较常见的是用 ReportNG 替代 TestNG 默认报告。有时间再另外说说。