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.htmlemailable-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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/695737.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记一次 migo 报错 M7097 没有可用于物料 XXX 的库存过账

背景:公司重构SAP后&#xff0c;引入返利物料&#xff0c;此部分物料的数量统计单位是USD/CNY,不启用会计类视图&#xff0c;但是启用批次管理&#xff0c;但是正常物料不启用批次管理。这是大背景&#xff0c;物料类型为ZZZZ 但是实际需要的是 检查物料还是没有被用作其他方…

数字之美:探索人工智能绘画的奇妙世界

目录 引言AI绘画的定义与发展历程定义与发展历程AI绘画产品有哪些? AI绘画的应用领域设计与创意产业影视与游戏制作数字艺术与展览 AI绘画的基本原理与技术深度学习与神经网络生成对抗网络&#xff08;GAN&#xff09;风格迁移算法 AI绘画效果展示一只带着墨镜的小猫在高楼林立…

echarts 设置柱状图边框颜色

代码如下&#xff1a; this.options {tooltip: {trigger: "axis",textStyle: {align: "left",},className: "custom-tooltip-box",formatter: function (params) {return <div classcustom-tooltip-style><div classtitle><spa…

教学方法创新措施有哪些内容

在教育的世界里&#xff0c;每一位老师都是一位探索者&#xff0c;他们肩负着在知识的海洋中为学生引路的重任。然而&#xff0c;面对日新月异的知识更新和技术发展&#xff0c;传统的教学方法是否还能满足学生的需求&#xff1f; 看看老师们是如何将课堂变成一场场知识与智慧的…

navicat连接云服务器(宝塔)

下面介绍两种navicat连接云服务器&#xff08;宝塔&#xff09;的方法 一、通过ssh配置&#xff08;安全&#xff09; 打开navicat&#xff0c;配置新链接的SSH&#xff08;主机&#xff1a;填写公网IP&#xff0c;用户名和密码是服务器的账号密码&#xff09; 在常规填写数据…

服务器运维小技巧(三)——如何进行服务器批量管理

运维工程师在进行服务器运维时&#xff0c;往往一个人要同时监控几十甚至成百上千的机器&#xff0c;当机器数量增加时&#xff0c;服务器管理的难度将会大大增加。很多工程师在工作中会使用一些运维面板&#xff0c;比如bt&#xff0c;1panel等&#xff0c;但是这些工具往往一…

BLUEZ学习笔记_GATT_server_client_简单解析

文章参考了以下内容 蓝牙bluez5的开发方法及入门教程_bluez蓝牙配网demo-CSDN博客文章浏览阅读1w次&#xff0c;点赞15次&#xff0c;收藏99次。1 摘要这篇文章的主要目的是告诉大家应该如何使用bluez进行开发&#xff0c;由于bluez的文档实在太少了&#xff0c;入门门槛实在太…

微服务篇之分布式系统理论

一、CAP定理 1.什么是CAP 1998年&#xff0c;加州大学的计算机科学家 Eric Brewer 提出&#xff0c;分布式系统有三个指标&#xff1a; 1. Consistency&#xff08;一致性&#xff09;。 2. Availability&#xff08;可用性&#xff09;。 3. Partition tolerance &#xff0…

我是这样通过CATTI考试的,没办法,必须考!原创首发

2023年“侥幸”通过CATTI英语二级笔译。11月初考试&#xff0c;按官方原计划应该是2024年1月初公布考试成绩&#xff0c;但12月底就突然出分了。当时正好在上班&#xff0c;忙里偷闲登录网址、查分&#xff0c;没有想象中的那么激动&#xff0c;一切平淡如水。随后&#xff0c;…

智慧交通系统的开发流程

智慧交通是以互联网、物联网等网络组合为基础&#xff0c;以智慧路网、智慧装备、智慧出行、智慧管理为重要内容的交通发展新模式&#xff0c;具有信息联通、实时监控、管理协同、人物合一的基本特征。智慧交通系统的开发流程可以按以下步骤进行&#xff0c;希望对大家有所帮助…

OpenCV人脸检测案例实战

人脸检测是一种计算机视觉技术&#xff0c;旨在识别图像或视频中的人脸。这项技术的基本内容包括使用特定的算法和模型来定位和识别人脸&#xff0c;通常涉及在图像中寻找面部特征&#xff0c;如眼睛、鼻子、嘴巴等&#xff0c;以便准确地确定人脸的位置和边界。人脸检测技术的…

Kotlin 进阶版 协程

kotlin是协程的一种实现 Dispatchers.IO&#xff1a;适用于执行磁盘或网络 I/O 操作的调度器&#xff0c;例如文件读写、网络请求等。在 Android 中&#xff0c;Dispatchers.IO 会使用一个专门的线程池来处理这些操作&#xff0c;以防止阻塞主线程。 Dispatchers.Main&#xf…

ALINX黑金AXU3EGB 开发板用户手册 CAN接口信号方向标识错误说明

如上篇文章 CAN收发器 SN65HVD232 的D R引脚方向是 D是输入&#xff0c;R是输出。 https://blog.csdn.net/zhengwenbang/article/details/136151668?spm1001.2014.3001.5501 因此 ALINX黑金AXU3EGB 用户手册 Page 43页 图 3-10-1 PS 端 CAN 收发芯片的连接示意图&#xff0c;…

刚开的抖店怎样推广?找主播带货,积累资源/渠道,拉动自然流量成交

我是王路飞。 2024年&#xff0c;依旧有很多人想入局抖音小店。 刚复工没几天&#xff0c;我就已经收到好多粉丝朋友的私信了&#xff0c;纷纷表示自己已经开通了抖店了&#xff0c;但是不会运营&#xff0c;现在新店应该怎样进行推广呢&#xff1f; 这篇内容就给你们详细说…

玩转网络抓包利器:Wireshark常用协议分析讲解

Wireshark是一个开源的网络协议分析工具&#xff0c;它能够捕获和分析网络数据包&#xff0c;并以用户友好的方式呈现这些数据包的内容。Wireshark 被广泛应用于网络故障排查、安全审计、教育及软件开发等领域。关于该工具的安装请参考之前的文章&#xff1a;地址 &#xff0c;…

CVE-2024-0918 TEW-800MB RCE漏洞分析

漏洞描述 固件版本为1.0.1.0的TEW-800MB路由器存在命令注入漏洞。如果攻击者获得了web管理权限&#xff0c;他们可以将命令注入到httpd未知函数中的post请求参数DeviceURL中&#xff0c;从而获得shell权限。。 参考链接 TEW-800MB (notion.site)https://warp-desk-89d.notio…

【嵌入式学习】QT-Day2-Qt基础

1> 思维导图 https://lingjun.life/wiki/EmbeddedNote/20QT 2>登录界面优化 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff…

LWM(LargeWorldModel)大世界模型-可文字可图片可视频-多模态LargeWorld-详细安装记录

说明 Large World Model&#xff08;LWM&#xff09;是一个通用的大环境多模态自回归模型&#xff0c;它利用了一个技术名为RingAttention&#xff0c;通过在大型的多样化视频和图书数据集上的训练&#xff0c;实现了对语言、图像和视频的理解与生成能力。 在github上已有4.2k…

Spring Boot与Netty:构建高性能的网络应用

点击下载《Spring Boot与Netty&#xff1a;构建高性能的网络应用》 1. 前言 本文将详细探讨如何在Spring Boot应用中集成Netty&#xff0c;以构建高性能的网络应用。我们将首先了解Netty的原理和优势&#xff0c;然后介绍如何在Spring Boot项目中集成Netty&#xff0c;包括详…

JAVA高并发——单例模式和不变模式

文章目录 1、探讨单例模式2、不变模式 由于并行程序设计比串行程序设计复杂得多&#xff0c;因此我强烈建议大家了解一些常见的设计方法。就好像练习武术&#xff0c;一招一式都是要经过学习的。如果自己胡乱打&#xff0c;效果不见得好。前人会总结一些武术套路&#xff0c;对…