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组合代替报告中的namefor(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.GITHUBhtmlReporter.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.htmlFile reportFile = new File(reportPath, "report.html");// 邮件报告名emailable-report.htmlFile 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.htmlFile reportFile = new File(reportPath, "report.html");// 邮件报告名emailable-report.htmlFile 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.GITHUBhtmlReporter.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 {@Overridepublic Map<String, String> getSystemInfo() {Map<String, String> systemInfo = new HashMap<>();systemInfo.put("测试人员", "zuozewei");return systemInfo;}
}
可用于添加系统信息,例如:db的配置信息,人员信息,环境信息等。根据项目实际情况添加。
至此,extentreports 美化报告完成。
Step-6:添加测试用例
public class TestMethodsDemo {@Testpublic void test1(){Assert.assertEquals(1,2);}@Testpublic void test2(){Assert.assertEquals(1,1);}@Testpublic void test3(){Assert.assertEquals("aaa","aaa");}@Testpublic 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