TestNG与ExtentReport集成
目录
1 通过实现ITestListener的方法添加Reporter log
1.1 MyTestListener设置
1.2 输出结果
2 TestNG与ExtentReporter集成
2.1 项目结构
2.2 MyExtentReportListener设置
2.3 单多Suite、Test组合测试
2.3.1 单Suite单Test
2.3.2 单Suite多Test
2.3.3 多Suite
源代码:interface-test-framework.zip
1 通过实现ITestListener的方法添加Reporter log
TestNG的Listener列表
TestNG提供了一组预定义的Listener Java接口,这些接口全部继承自TestNG的 ITestNGListener接口。用户创建这些接口的实现类,并把它们加入到 TestNG 中,TestNG便会在测试运行的不同时刻调用这些类中的接口方法:
- IExecutionListener 监听TestNG运行的启动和停止。
- IAnnotationTransformer 注解转换器,用于TestNG测试类中的注解。
- ISuiteListener 测试套件监听器,监听测试套件的启动和停止。
- ITestListener 测试方法执行监听。
- IConfigurationListener 监听配置方法相关的接口。
- IMethodInterceptor 拦截器,调整测试方法的执行顺序。
- IInvokedMethodListener 测试方法拦截监听,用于获取被TestNG调用的在Method的Before 和After方法监听器。该方法只会被配置和测试方法调用。
- IHookable 若测试类实现了该接口,当@Test方法被发现时,它的run()方法将会被调用来替代@Test方法。这个测试方法通常在IHookCallBack的callback之上调用,比较适用于需要JASS授权的测试类。
- IReporter 实现该接口可以生成一份测试报告。
本文将着重介绍最常用到的两个Listener ITestListener与Ireporter接口。
1.1 MyTestListener设置
通过实现ITestListener的方法,添加Reporter.log
MyTestListener.java
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.Reporter;
public class MyTestListener implements ITestListener {
//用例执行结束后,用例执行成功时调用
public void onTestSuccess(ITestResult tr) {
logTestEnd(tr, "Success");
}
//用例执行结束后,用例执行失败时调用
public void onTestFailure(ITestResult tr) {
logTestEnd(tr, "Failed");
}
//用例执行结束后,用例执行skip时调用
public void onTestSkipped(ITestResult tr) {
logTestEnd(tr, "Skipped");
}
//每次方法失败但是已经使用successPercentage进行注释时调用,并且此失败仍保留在请求的成功百分比之内。
public void onTestFailedButWithinSuccessPercentage(ITestResult tr) {
logTestEnd(tr, "FailedButWithinSuccessPercentage");
}
//每次调用测试@Test之前调用
public void onTestStart(ITestResult result) {
logTestStart(result);
}
//在测试类被实例化之后调用,并在调用任何配置方法之前调用。
public void onStart(ITestContext context) {
return;
}
//在所有测试运行之后调用,并且所有的配置方法都被调用
public void onFinish(ITestContext context) {
return;
}
// 在用例执行结束时,打印用例的执行结果信息
protected void logTestEnd(ITestResult tr, String result) {
Reporter.log(String.format("-------------Result: %s-------------", result), true);
}
// 在用例开始时,打印用例的一些信息,比如@Test对应的方法名,用例的描述等等
protected void logTestStart(ITestResult tr) {
Reporter.log(String.format("-------------Run: %s.%s---------------", tr.getTestClass().getName(), tr.getMethod().getMethodName()), true);
Reporter.log(String.format("用例描述: %s, 优先级: %s", tr.getMethod().getDescription(), tr.getMethod().getPriority()),true);
return;
}
}
1.2 输出结果
SmkDemo1.java
import com.demo.listener.MyTestListener;
import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners({MyTestListener.class})
public class SmkDemo1 {
@Test(description="测testPass11的描述",priority = 1,groups = {"分组1"})
public void testPass11(){
Reporter.log("Test11的第一步",true);
Assert.assertEquals(1,1);
}
}
输出界面显示如下
图1 log to 输出界面
2 TestNG与ExtentReporter集成
2.1 项目结构
图2 项目结构
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>interface-test-framework</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 测试报告插件和testng的结合 -->
<dependency>
<groupId>com.vimalselvam</groupId>
<artifactId>testng-extentsreport</artifactId>
<version>1.3.1</version>
</dependency>
<!-- extentreports测试报告插件 -->
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.1.0</version>
</dependency>
</dependencies>
</project>
2.2 MyExtentReportListener设置
MyExtentReporterListener.java
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.ResourceCDN;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.ChartLocation;
import com.aventstack.extentreports.reporter.configuration.Theme;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.io.File;
import java.util.*;
public class MyExtentReporterListener implements IReporter {
//生成的路径以及文件名
private static final String OUTPUT_FOLDER = "test-output/";
private static final String FILE_NAME = "report.html";
private ExtentReports extent;
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
init();
boolean createSuiteNode = false;
if(suites.size()>1){
createSuiteNode=true;
}
for (ISuite suite : suites) {
Map<String, ISuiteResult> result = suite.getResults();
//如果suite里面没有任何用例,直接跳过,不在报告里生成
if(result.size()==0){
continue;
}
//统计suite下的成功、失败、跳过的总用例数
int suiteFailSize=0;
int suitePassSize=0;
int suiteSkipSize=0;
ExtentTest suiteTest=null;
//存在多个suite的情况下,在报告中将同一个一个suite的测试结果归为一类,创建一级节点。
if(createSuiteNode){
// suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName());
suiteTest = extent.createTest(suite.getName());
}
boolean createSuiteResultNode = false;
if(result.size()>1){
createSuiteResultNode=true;
}
for (ISuiteResult r : result.values()) {
ExtentTest resultNode=null;
ITestContext context = r.getTestContext();
if(createSuiteResultNode){
//没有创建suite的情况下,将在SuiteResult的创建为一级节点,否则创建为suite的一个子节点。
if( null == suiteTest){
resultNode = extent.createTest(r.getTestContext().getName());
}else{
resultNode = suiteTest.createNode(r.getTestContext().getName());
}
}else{
resultNode = suiteTest;
}
String[] categories=new String[1];
if(resultNode != null){
resultNode.getModel().setName(suite.getName()+"."+r.getTestContext().getName());
if(resultNode.getModel().hasCategory()){
resultNode.assignCategory(r.getTestContext().getName());
}else{
// resultNode.assignCategory(suite.getName(),r.getTestContext().getName());
categories[0]=suite.getName()+"."+r.getTestContext().getName();
}
resultNode.getModel().setStartTime(r.getTestContext().getStartDate());
resultNode.getModel().setEndTime(r.getTestContext().getEndDate());
//统计SuiteResult下的数据
int passSize = r.getTestContext().getPassedTests().size();
int failSize = r.getTestContext().getFailedTests().size();
int skipSize = r.getTestContext().getSkippedTests().size();
suitePassSize += passSize;
suiteFailSize += failSize;
suiteSkipSize += skipSize;
if(failSize>0){
resultNode.getModel().setStatus(Status.FAIL);
}
resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",passSize,failSize,skipSize));
}
buildTestNodes(resultNode,categories,context.getFailedTests(), Status.FAIL);
buildTestNodes(resultNode,categories,context.getSkippedTests(), Status.SKIP);
buildTestNodes(resultNode,categories,context.getPassedTests(), Status.PASS);
}
if(suiteTest!= null){
suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",suitePassSize,suiteFailSize,suiteSkipSize));
if(suiteFailSize>0){
suiteTest.getModel().setStatus(Status.FAIL);
}
}
}
// for (String s : Reporter.getOutput()) {
// extent.setTestRunnerOutput(s);
// }
extent.flush();
}
private void init() {
//文件夹不存在的话进行创建
File reportDir= new File(OUTPUT_FOLDER);
if(!reportDir.exists()&& !reportDir .isDirectory()){
reportDir.mkdir();
}
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
// 设置静态文件的DNS
//解决cdn访问不了的问题
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
htmlReporter.config().setDocumentTitle("api自动化测试报告");
htmlReporter.config().setReportName("api自动化测试报告");
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setCSS(".node.level-1 ul{ display:none;} .node.level-1.active ul{display:block;}");
extent = new ExtentReports();
extent.attachReporter(htmlReporter);
extent.setReportUsesManualConfiguration(true);
}
private void buildTestNodes(ExtentTest extenttest, String[] categories, IResultMap tests, Status status) {
// //存在父节点时,获取父节点的标签
// String[] categories=new String[0];
// if(extenttest != null ){
// List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll();
// categories = new String[categoryList.size()];
// for(int index=0;index<categoryList.size();index++){
// categories[index] = categoryList.get(index).getName();
// }
// }
ExtentTest test;
if (tests.size() > 0) {
//调整用例排序,按时间排序
Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() {
public int compare(ITestResult o1, ITestResult o2) {
return o1.getStartMillis()<o2.getStartMillis()?-1:1;
}
});
treeSet.addAll(tests.getAllResults());
for (ITestResult result : treeSet) {
Object[] parameters = result.getParameters();
String name="";
//如果有参数,则使用参数的toString组合代替报告中的name
for(Object param:parameters){
name+=param.toString();
}
if(name.length()>0){
if(name.length()>50){
name= name.substring(0,49)+"...";
}
}else{
name = result.getMethod().getMethodName();
}
if(extenttest==null){
test = extent.createTest(name);
}else{
//作为子节点进行创建时,设置同父节点的标签一致,便于报告检索。
test = extenttest.createNode(name).assignCategory(categories);
}
//test.getModel().setDescription(description.toString());
//test = extent.createTest(result.getMethod().getMethodName());
for (String group : result.getMethod().getGroups())
test.assignCategory(group);
List<String> outputList = Reporter.getOutput(result);
for(String output:outputList){
//将用例的log输出报告中
test.debug(output);
}
if (result.getThrowable() != null) {
test.log(status, result.getThrowable());
}
else {
test.log(status, "Test " + status.toString().toLowerCase() + "ed");
}
test.getModel().setStartTime(getTime(result.getStartMillis()));
test.getModel().setEndTime(getTime(result.getEndMillis()));
}
}
}
private Date getTime(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return calendar.getTime();
}
}
2.3 单多Suite、Test组合测试
2.3.1 单Suite单Test
testngSingleSuiteSingleTest.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="SingleSuite">
<test name="SingleTest" verbose="1" preserve-order="true" >
<classes>
<class name="com.demo.testcase.smoke.SmkDemo1">
</class>
<class name="com.demo.testcase.sit.SitDemo2">
</class>
</classes>
</test>
<!--配置监听器-->
<listeners>
<listener class-name="com.demo.listener.MyTestListener"/>
<listener class-name="com.demo.listener.MyExtentReporterListener"/>
</listeners>
</suite>
图3 单Suite单Test总览
图4 单Suite单Test分组
图5 单Suite单Test错误分组
图6 单Suite单Test Dashboard
2.3.2 单Suite多Test
testngSingleSuiteDoubleTest.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="SingleSuite">
<test name="DoubleTest1" verbose="1" preserve-order="true" >
<classes>
<class name="com.demo.testcase.smoke.SmkDemo1">
</class>
</classes>
</test>
<test name="DoubleTest2" verbose="1" preserve-order="true" >
<classes>
<class name="com.demo.testcase.sit.SitDemo2">
</class>
</classes>
</test>
<!--配置监听器-->
<listeners>
<listener class-name="com.demo.listener.MyTestListener"/>
<listener class-name="com.demo.listener.MyExtentReporterListener"/>
</listeners>
</suite>
图7 单Suite多Test总览
图8 单Suite多Test分组
2.3.3 多Suite
testngDoubleSuite.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="DoubleSuite">
<suite-files>
<suite-file path="testngSingleSuiteSingleTest.xml"/>
<suite-file path="testngSingleSuiteDoubleTest.xml"/>
</suite-files>
<!--配置监听器-->
<listeners>
<listener class-name="com.demo.listener.MyTestListener"/>
<listener class-name="com.demo.listener.MyExtentReporterListener"/>
</listeners>
</suite>
图9 多Suite总览1
图10 多Suite总览2
图11 多Suite分组
参考
[1] testng框架Listener介绍及测试结果的收集
[2] TestNG执行的日志ITestListener与结果IReporter
[3] TestNG执行的日志ITestListener与结果IReporter
[4] TestNg Beginner’s Guide–阅后总结之Textng.xml
[5] TestNg Beginner’s Guide–阅后总结之TestNg注解
ExtentReports 另一种方法
引言
在走进Java接口测试之测试框架TestNG 中我们详细介绍了 TestNG 的各种用法, 在本文中,我将详细介绍如何将 ExtentReports 测试报告与TestNG集成。
ExtentReports 简介
主要特点:
- 生成的报告简洁美观
- 生成的单html方便 Jenkins 集成发邮件
- 自带集中展示历史报告的服务端
- 支持 Java 和 .Net
TestNG 原生报告有点丑,信息整理有点乱。ExtentReports 是用于替换TestNG 原生报告。当然也可以使用 ReportNg,个人偏好 ExtentReports 样式。
官网已经给了很多demo了,大家可以参考练习。
官网:http://extentreports.com/
客户端:
https://github.com/anshooarora/extentreports-java/commits/master
服务端:https://github.com/anshooarora/extentx
Step-1:添加 Maven 依赖包
引入pom.xml
<!--引入extentreports相关包-->
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.1.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.vimalselvam</groupId>
<artifactId>testng-extentsreport</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.relevantcodes</groupId>
<artifactId>extentreports</artifactId>
<version>2.41.2</version>
</dependency>
<!--引入testng测试框架-->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
<scope>compile</scope>
</dependency>
Step-2:重写 ExtentTestNgFormatter 类
主要基于以下两项原因:
- 支持报告中展示更多状态类型的测试结果,例如:成功、失败、警告、跳过等。
- 因为不支持cdn.rawgit.com访问,故替css访问方式。
创建 MyExtentTestNgFormatter 类
下载 ExtentReportes 源码,找到 ExtentTestNgFormatter 类,Listener 目录下创建 MyExtentTestNgFormatter.java
类直接继承 ExtentTestNgFormatter 类。
public class MyExtentTestNgFormatter extends ExtentTestNgFormatter {
解决CDN无法访问
构造方法加入
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
具体代码如下:
public MyExtentTestNgFormatter() {
setInstance(this);
testRunnerOutput = new ArrayList<>();
String reportPathStr = System.getProperty("reportPath");
File reportPath;
try {
reportPath = new File(reportPathStr);
} catch (NullPointerException e) {
reportPath = new File(TestNG.DEFAULT_OUTPUTDIR);
}
if (!reportPath.exists()) {
if (!reportPath.mkdirs()) {
throw new RuntimeException("Failed to create output run directory");
}
}
File reportFile = new File(reportPath, "report.html");
File emailReportFile = new File(reportPath, "emailable-report.html");
htmlReporter = new ExtentHtmlReporter(reportFile);
EmailReporter emailReporter = new EmailReporter(emailReportFile);
reporter = new ExtentReports();
// 如果cdn.rawgit.com访问不了,可以设置为:ResourceCDN.EXTENTREPORTS或者ResourceCDN.GITHUB
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
reporter.attachReporter(htmlReporter, emailReporter);
}
重写 onstart 方法
新建一个类名为MyReporter,一个静态ExtentTest的引用。
Listener 包下 MyReporter.java
public class MyReporter { public static ExtentTest report; }
MyExtentTestNgFormatter.java
public void onStart(ITestContext iTestContext) {
ISuite iSuite = iTestContext.getSuite();
ExtentTest suite = (ExtentTest) iSuite.getAttribute(SUITE_ATTR);
ExtentTest testContext = suite.createNode(iTestContext.getName());
// 将MyReporter.report静态引用赋值为testContext。
// testContext是@Test每个测试用例时需要的。report.log可以跟随具体的测试用例。另请查阅源码。
MyReporter.report = testContext;
iTestContext.setAttribute("testContext", testContext);
}
自定义配置
测试报告默认是在工程根目录下创建 test-output/
文件夹下,名为 report.html
、 emailable-report.html
。可根据各自需求在构造方法中修改。
public MyExtentTestNgFormatter() {
setInstance(this);
testRunnerOutput = new ArrayList<>();
// reportPath报告路径
String reportPathStr = System.getProperty("reportPath");
File reportPath;
try {
reportPath = new File(reportPathStr);
} catch (NullPointerException e) {
reportPath = new File(TestNG.DEFAULT_OUTPUTDIR);
}
if (!reportPath.exists()) {
if (!reportPath.mkdirs()) {
throw new RuntimeException("Failed to create output run directory");
}
}
// 报告名report.html
File reportFile = new File(reportPath, "report.html");
// 邮件报告名emailable-report.html
File emailReportFile = new File(reportPath, "emailable-report.html");
htmlReporter = new ExtentHtmlReporter(reportFile);
EmailReporter emailReporter = new EmailReporter(emailReportFile);
reporter = new ExtentReports();
reporter.attachReporter(htmlReporter, emailReporter);
}
report.log
report.log 支持多种玩法
// 根据状态不同添加报告。型如警告 MyReporter.report.log(Status.WARNING, "接口耗时(ms):" + String.valueOf(time));
直接从TestClass 中运行时会报 MyReporter.report
的空指针错误,需做判空处理。
完整代码
package com.ruoyi.listener;
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.ResourceCDN;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.vimalselvam.testng.EmailReporter;
import com.vimalselvam.testng.NodeName;
import com.vimalselvam.testng.SystemInfo;
import com.vimalselvam.testng.listener.ExtentTestNgFormatter;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MyExtentTestNgFormatter extends ExtentTestNgFormatter {
private static final String REPORTER_ATTR = "extentTestNgReporter";
private static final String SUITE_ATTR = "extentTestNgSuite";
private ExtentReports reporter;
private List<String> testRunnerOutput;
private Map<String, String> systemInfo;
private ExtentHtmlReporter htmlReporter;
private static ExtentTestNgFormatter instance;
public MyExtentTestNgFormatter() {
setInstance(this);
testRunnerOutput = new ArrayList<>();
// reportPath 报告路径
String reportPathStr = System.getProperty("reportPath");
File reportPath;
try {
reportPath = new File(reportPathStr);
} catch (NullPointerException e) {
reportPath = new File(TestNG.DEFAULT_OUTPUTDIR);
}
if (!reportPath.exists()) {
if (!reportPath.mkdirs()) {
throw new RuntimeException("Failed to create output run directory");
}
}
// 报告名report.html
File reportFile = new File(reportPath, "report.html");
// 邮件报告名emailable-report.html
File emailReportFile = new File(reportPath, "emailable-report.html");
htmlReporter = new ExtentHtmlReporter(reportFile);
EmailReporter emailReporter = new EmailReporter(emailReportFile);
reporter = new ExtentReports();
// 如果cdn.rawgit.com访问不了,可以设置为:ResourceCDN.EXTENTREPORTS或者ResourceCDN.GITHUB
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
reporter.attachReporter(htmlReporter, emailReporter);
}
/**
* Gets the instance of the {@link ExtentTestNgFormatter}
*
* @return The instance of the {@link ExtentTestNgFormatter}
*/
public static ExtentTestNgFormatter getInstance() {
return instance;
}
private static void setInstance(ExtentTestNgFormatter formatter) {
instance = formatter;
}
/**
* Gets the system information map
*
* @return The system information map
*/
public Map<String, String> getSystemInfo() {
return systemInfo;
}
/**
* Sets the system information
*
* @param systemInfo The system information map
*/
public void setSystemInfo(Map<String, String> systemInfo) {
this.systemInfo = systemInfo;
}
public void onStart(ISuite iSuite) {
if (iSuite.getXmlSuite().getTests().size() > 0) {
ExtentTest suite = reporter.createTest(iSuite.getName());
String configFile = iSuite.getParameter("report.config");
if (!Strings.isNullOrEmpty(configFile)) {
htmlReporter.loadXMLConfig(configFile);
}
String systemInfoCustomImplName = iSuite.getParameter("system.info");
if (!Strings.isNullOrEmpty(systemInfoCustomImplName)) {
generateSystemInfo(systemInfoCustomImplName);
}
iSuite.setAttribute(REPORTER_ATTR, reporter);
iSuite.setAttribute(SUITE_ATTR, suite);
}
}
private void generateSystemInfo(String systemInfoCustomImplName) {
try {
Class<?> systemInfoCustomImplClazz = Class.forName(systemInfoCustomImplName);
if (!SystemInfo.class.isAssignableFrom(systemInfoCustomImplClazz)) {
throw new IllegalArgumentException("The given system.info class name <" + systemInfoCustomImplName +
"> should implement the interface <" + SystemInfo.class.getName() + ">");
}
SystemInfo t = (SystemInfo) systemInfoCustomImplClazz.newInstance();
setSystemInfo(t.getSystemInfo());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
public void onFinish(ISuite iSuite) {
}
public void onTestStart(ITestResult iTestResult) {
MyReporter.setTestName(iTestResult.getName());
}
public void onTestSuccess(ITestResult iTestResult) {
}
public void onTestFailure(ITestResult iTestResult) {
}
public void onTestSkipped(ITestResult iTestResult) {
}
public void onTestFailedButWithinSuccessPercentage(ITestResult iTestResult) {
}
public void onStart(ITestContext iTestContext) {
ISuite iSuite = iTestContext.getSuite();
ExtentTest suite = (ExtentTest) iSuite.getAttribute(SUITE_ATTR);
ExtentTest testContext = suite.createNode(iTestContext.getName());
// 自定义报告
// 将MyReporter.report 静态引用赋值为 testContext。
// testContext 是 @Test每个测试用例时需要的。report.log可以跟随具体的测试用例。另请查阅源码。
MyReporter.report = testContext;
iTestContext.setAttribute("testContext", testContext);
}
public void onFinish(ITestContext iTestContext) {
ExtentTest testContext = (ExtentTest) iTestContext.getAttribute("testContext");
if (iTestContext.getFailedTests().size() > 0) {
testContext.fail("Failed");
} else if (iTestContext.getSkippedTests().size() > 0) {
testContext.skip("Skipped");
} else {
testContext.pass("Passed");
}
}
public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
if (iInvokedMethod.isTestMethod()) {
ITestContext iTestContext = iTestResult.getTestContext();
ExtentTest testContext = (ExtentTest) iTestContext.getAttribute("testContext");
ExtentTest test = testContext.createNode(iTestResult.getName(), iInvokedMethod.getTestMethod().getDescription());
iTestResult.setAttribute("test", test);
}
}
public void afterInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
if (iInvokedMethod.isTestMethod()) {
ExtentTest test = (ExtentTest) iTestResult.getAttribute("test");
List<String> logs = Reporter.getOutput(iTestResult);
for (String log : logs) {
test.info(log);
}
int status = iTestResult.getStatus();
if (ITestResult.SUCCESS == status) {
test.pass("Passed");
} else if (ITestResult.FAILURE == status) {
test.fail(iTestResult.getThrowable());
} else {
test.skip("Skipped");
}
for (String group : iInvokedMethod.getTestMethod().getGroups()) {
test.assignCategory(group);
}
}
}
/**
* Adds a screen shot image file to the report. This method should be used only in the configuration method
* and the {@link ITestResult} is the mandatory parameter
*
* @param iTestResult The {@link ITestResult} object
* @param filePath The image file path
* @throws IOException {@link IOException}
*/
public void addScreenCaptureFromPath(ITestResult iTestResult, String filePath) throws IOException {
ExtentTest test = (ExtentTest) iTestResult.getAttribute("test");
test.addScreenCaptureFromPath(filePath);
}
/**
* Adds a screen shot image file to the report. This method should be used only in the
* {@link org.testng.annotations.Test} annotated method
*
* @param filePath The image file path
* @throws IOException {@link IOException}
*/
public void addScreenCaptureFromPath(String filePath) throws IOException {
ITestResult iTestResult = Reporter.getCurrentTestResult();
Preconditions.checkState(iTestResult != null);
ExtentTest test = (ExtentTest) iTestResult.getAttribute("test");
test.addScreenCaptureFromPath(filePath);
}
/**
* Sets the test runner output
*
* @param message The message to be logged
*/
public void setTestRunnerOutput(String message) {
testRunnerOutput.add(message);
}
public void generateReport(List<XmlSuite> list, List<ISuite> list1, String s) {
if (getSystemInfo() != null) {
for (Map.Entry<String, String> entry : getSystemInfo().entrySet()) {
reporter.setSystemInfo(entry.getKey(), entry.getValue());
}
}
reporter.setTestRunnerOutput(testRunnerOutput);
reporter.flush();
}
/**
* Adds the new node to the test. The node name should have been set already using {@link NodeName}
*/
public void addNewNodeToTest() {
addNewNodeToTest(NodeName.getNodeName());
}
/**
* Adds the new node to the test with the given node name.
*
* @param nodeName The name of the node to be created
*/
public void addNewNodeToTest(String nodeName) {
addNewNode("test", nodeName);
}
/**
* Adds a new node to the suite. The node name should have been set already using {@link NodeName}
*/
public void addNewNodeToSuite() {
addNewNodeToSuite(NodeName.getNodeName());
}
/**
* Adds a new node to the suite with the given node name
*
* @param nodeName The name of the node to be created
*/
public void addNewNodeToSuite(String nodeName) {
addNewNode(SUITE_ATTR, nodeName);
}
private void addNewNode(String parent, String nodeName) {
ITestResult result = Reporter.getCurrentTestResult();
Preconditions.checkState(result != null);
ExtentTest parentNode = (ExtentTest) result.getAttribute(parent);
ExtentTest childNode = parentNode.createNode(nodeName);
result.setAttribute(nodeName, childNode);
}
/**
* Adds a info log message to the node. The node name should have been set already using {@link NodeName}
*
* @param logMessage The log message string
*/
public void addInfoLogToNode(String logMessage) {
addInfoLogToNode(logMessage, NodeName.getNodeName());
}
/**
* Adds a info log message to the node
*
* @param logMessage The log message string
* @param nodeName The name of the node
*/
public void addInfoLogToNode(String logMessage, String nodeName) {
ITestResult result = Reporter.getCurrentTestResult();
Preconditions.checkState(result != null);
ExtentTest test = (ExtentTest) result.getAttribute(nodeName);
test.info(logMessage);
}
/**
* Marks the node as failed. The node name should have been set already using {@link NodeName}
*
* @param t The {@link Throwable} object
*/
public void failTheNode(Throwable t) {
failTheNode(NodeName.getNodeName(), t);
}
/**
* Marks the given node as failed
*
* @param nodeName The name of the node
* @param t The {@link Throwable} object
*/
public void failTheNode(String nodeName, Throwable t) {
ITestResult result = Reporter.getCurrentTestResult();
Preconditions.checkState(result != null);
ExtentTest test = (ExtentTest) result.getAttribute(nodeName);
test.fail(t);
}
/**
* Marks the node as failed. The node name should have been set already using {@link NodeName}
*
* @param logMessage The message to be logged
*/
public void failTheNode(String logMessage) {
failTheNode(NodeName.getNodeName(), logMessage);
}
/**
* Marks the given node as failed
*
* @param nodeName The name of the node
* @param logMessage The message to be logged
*/
public void failTheNode(String nodeName, String logMessage) {
ITestResult result = Reporter.getCurrentTestResult();
Preconditions.checkState(result != null);
ExtentTest test = (ExtentTest) result.getAttribute(nodeName);
test.fail(logMessage);
}
}
class MyReporter {
public static ExtentTest report;
private static String testName;
public static String getTestName() {
return testName;
}
public static void setTestName(String testName) {
MyReporter.testName = testName;
}
}
Step-3:配置监听
在测试集合 testng.xml 文件中导入 Listener 监听类。
<listeners>
<listener class-name="com.zuozewei.extentreportdemo.listener.MyExtentTestNgFormatter"/>
</listeners>
Step-4:配置报告
extent reporters
支持报告的配置。目前支持的配置内容有title、主题等。 先在 src/resources/
目录下添加 config/report/extent-config.xml
。
<?xml version="1.0" encoding="UTF-8"?>
<extentreports>
<configuration>
<timeStampFormat>yyyy-MM-dd HH:mm:ss</timeStampFormat>
<!-- report theme -->
<!-- standard, dark 个人喜好暗色 -->
<theme>dark</theme>
<!-- document encoding -->
<!-- defaults to UTF-8 -->
<encoding>UTF-8</encoding>
<!-- protocol for script and stylesheets -->
<!-- defaults to https -->
<protocol>https</protocol>
<!-- title of the document -->
<documentTitle>接口自动化测试报告</documentTitle>
<!-- report name - displayed at top-nav -->
<reportName>接口自动化测试报告</reportName>
<!-- report headline - displayed at top-nav, after reportHeadline -->
<reportHeadline>接口自动化测试报告</reportHeadline>
<!-- global date format override -->
<!-- defaults to yyyy-MM-dd -->
<dateFormat>yyyy-MM-dd</dateFormat>
<!-- global time format override -->
<!-- defaults to HH:mm:ss -->
<timeFormat>HH:mm:ss</timeFormat>
<!-- custom javascript -->
<scripts>
<![CDATA[
$(document).ready(function() {
});
]]>
</scripts>
<!-- custom styles -->
<styles>
<![CDATA[
]]>
</styles>
</configuration>
</extentreports>
Step-5:配置系统
config下新建 MySystemInfo类继承 SystemInfo 接口
public class MySystemInfo implements SystemInfo {
@Override
public Map<String, String> getSystemInfo() {
Map<String, String> systemInfo = new HashMap<>();
systemInfo.put("测试人员", "zuozewei");
return systemInfo;
}
}
可用于添加系统信息,例如:db的配置信息,人员信息,环境信息等。根据项目实际情况添加。
至此,extentreports 美化报告完成。
Step-6:添加测试用例
public class TestMethodsDemo {
@Test
public void test1(){
Assert.assertEquals(1,2);
}
@Test
public void test2(){
Assert.assertEquals(1,1);
}
@Test
public void test3(){
Assert.assertEquals("aaa","aaa");
}
@Test
public void logDemo(){
Reporter.log("这是故意写入的日志");
throw new RuntimeException("故意运行时异常");
}
}
Step-7:测试用例suite
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="测试demo" verbose="1" preserve-order="true">
<parameter name="report.config" value="src/main/resources/report/extent-config.xml"/>
<parameter name="system.info" value="com.zuozewei.extentreportdemo.config.MySystemInfo"/>
<test name="测试demo" preserve-order="true">
<classes>
<class name="com.zuozewei.extentreportdemo.testCase.TestMethodsDemo"/>
</classes>
</test>
<listeners>
<listener class-name="com.zuozewei.extentreportdemo.listener.MyExtentTestNgFormatter"/>
</listeners>
</suite>
测试报告
HTML Resport 示例
Email Report 示例
工程目录
本文源码:
https://github.com/7DGroup/Java-API-Test-Examples