TestNG与ExtentReport单元测试导出报告文档
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
相关文章:

TestNG与ExtentReport单元测试导出报告文档
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…...

【JavaEE】_form表单构造HTTP请求
目录 1. form表单的格式 1.1 form表单的常用属性 1.2 form表单的常用搭配标签:input 2. form表单构造GET请求实例 3. form表单构造POST请求实例 4. form表单构造法的缺陷 对于客户端浏览器,以下操作即构造了HTTP请求: 1. 直接在浏览器…...
Mysql中INFORMATION_SCHEMA虚拟库使用
虚拟库字段讲解 #查看INFORMATION_SCHEMA的表信息 DESC information_schema.tables; 重要列: TABLE_SCHEMA #表所在的库 TABLE_NAME #表名 ENGINE #表的存储引擎 TABLE_ROWS #表的行数 DATA_LENGTH #表数据行占用的字节数 AVG_ROW_LENGTH #平均行长度 INDEX_LENGTH…...

【《高性能 MySQL》摘录】第 2 章 MySQL 基准测试
文章目录 2.1 为什么需要基准测试2.2 基准测试的策略2.2.1 测试何种指标 2.3 基准测试方法2.3.1 设计和规划基准测试2.3.2 基准测试应该运行多长时间2.3.3 获取系统性能和状态2.3.4 获得准确的测试结果2.3.5 运行基准测试并分析结果2.3.6 绘图的重要性 2.4 基准测试工具…...

常用的Web应用程序的自动测试工具有哪些
在Web应用程序的自动化测试领域,有许多流行的工具可供选择。以下是一些常用的Web自动化测试工具: 1. Selenium - Selenium是最流行的开源Web应用程序自动化测试套件之一。 - 它支持多种编程语言,如Java、C#、Python、Ruby等。 …...
人工智能与开源机器学习框架
链接:华为机考原题 TensorFlow是一个开源的机器学习框架,由Google开发和维护。它提供了一个针对神经网络和深度学习的强大工具集,能够帮助开发人员构建和训练各种机器学习模型。 TensorFlow的基本概念包括: 张量(Ten…...

高通XBL阶段读取分区
【需求】: 在某些场景下,需要在XBL阶段读取分区数据,需要验证xbl阶段方案 这里主要以裸分区为例,比如oem分区。 1、创建一个1MB大小的oem.img,写入内容“test oem partition” 创建方式: dd if/dev/null …...

[极客大挑战2019]upload
该题考点:后缀黑名单文件内容过滤php木马的几种书写方法 phtml可以解析php代码;<script language"php">eval($_POST[cmd]);</script> 犯蠢的点儿:利用html、php空格和php.不解析<script language"php"&…...
[FastDDS] 基于eProsima FastDDS的移动机器人数据中间件
[FastDDS] 基于eProsima FastDDS的移动机器人数据中间件 注明:无 本栏目主要讲述,基于eProsima FastDDS的移动机器人数据中间件的实现、使用、性能测试。 What is [ FastDDS ]: eProsima Fast DDS是DDS(数据分发服务)规范的C实现…...

实现外网手机或者电脑随时随地远程访问家里的电脑主机(linux为例)
文章目录 一、背景概要二、安装配置花生壳软件(linux版本)三、手机端(外网)验证连接四、安装ubuntu20server版系统遇到的问题记录 一、背景概要 由于经常在遇到某些问题的时候,针对某一个场景的理解,需要借助于自己的电脑去编译(aosp/linux/qemu)代码查…...

spring boot集成redis
引入依赖 <!-- redis依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 连接池依赖 --><dependency><groupId>org.ap…...
Docker的常用命令
Docker的常用命令 Docker是一个开源的应用容器引擎,它使得开发者能够打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间没有任何接口ÿ…...
JSON简介与基本使用
JSON简介与基本使用 引言 在现今的互联网开发中,数据交换格式的选择至关重要。其中,JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,因其简洁、易读和易写的特性而备受青睐。本文将简要介绍JSON的基…...

好物周刊#40:多功能文件管理器
https://github.com/cunyu1943/JavaPark https://yuque.com/cunyu1943 村雨遥的好物周刊,记录每周看到的有价值的信息,主要针对计算机领域,每周五发布。 一、项目 1. 中国节假日补班日历 中国节假日、调休、补班日历,ICS 格式…...

【洛谷 P8780】[蓝桥杯 2022 省 B] 刷题统计 题解(贪心算法+模拟+四则运算)
[蓝桥杯 2022 省 B] 刷题统计 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a a a 道题目,周六和周日每天做 b b b 道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n n n 题? 输入格式 输入一…...
【蓝桥杯入门记录】静态数码管例程
目录 一、补充 (code) 二、例程 (1)例程1:数码管显示某一位(某一杠)。以点亮8段数码管最上面的横杠为例。 (2)例程2:数码管的8个段依次点亮(其他…...
6.openEuler系统服务的配置和管理(二)
openEuler OECA认证辅导,标红的文字为学习重点和考点。 如果需要做实验,建议安装麒麟信安、银河麒麟、统信等具有图形化的操作系统,其安装与openeuler基本一致。 3.任务管理 任务的概念和相关术语: 当你在终端或控制台工作时,可能不希望由于运行一个作业而占住了屏幕,因…...

一招鲜吃遍天!ChatGPT高级咒语揭秘:记忆、洗稿、速写SEO文章(一)
🌟 摘要 🌟 这个专栏系列的初衷是针对特定痛点精心设计GPT提示词,在这篇文章中,我们深入探讨了利用GPT技术解决三个常见挑战:增强记忆力、内容创新、以及SEO文章速写的高级技巧。这些挑战分别对应三个独特的解决策略,我们将逐一详细解析。 首先,解决记忆增强的挑战,我…...
LeetCode 每日一题 2024/2/19-2024/2/25
记录了初步解题思路 以及本地实现代码;并不一定为最优 也希望大家能一起探讨 一起进步 目录 2/19 590. N 叉树的后序遍历2/20 105. 从前序与中序遍历序列构造二叉树2/21 106. 从中序与后序遍历序列构造二叉树2/22 889. 根据前序和后序遍历构造二叉树2/23 2583. 二叉…...

Javaweb之SpringBootWeb案例之配置优先级的详细解析
1. 配置优先级 在我们前面的课程当中,我们已经讲解了SpringBoot项目当中支持的三类配置文件: application.properties application.yml application.yaml 在SpringBoot项目当中,我们要想配置一个属性,可以通过这三种方式当中…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...

iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...