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.rhkb.cn/news/261485.html

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

相关文章

问题:Spark SQL 读不到 Flink 写入 Hudi 表的新数据,打开新 Session 才可见

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

k8s版本升级后,pod一直无法被调度

故障现象&#xff1a;升级完k8s后&#xff0c;部署pod时发现pod无法被调度到node节点&#xff0c;pod的状态一直是pending状态。 报错内容&#xff1a;User-scheduler pods fail with “no kind “KubeSchedulerConfiguration” is registered for version “kubescheduler.co…

HCIP---OSPF综合实验

一&#xff1a;实验要求 1、R4为ISP&#xff0c;其上只能配置IP地址&#xff0c;R4与其所有直连设备间均使用公有IP 2、R3-R5/6/7为MGRE环境&#xff0c;R3为中心站点 3、整个OSPF环境IP基于R4的环回 4、所有设备均可访问R4的环回 5、减少LSA的更新量&#xff0c;加快收敛…

怿星科技测试实验室(EPT LABS)服务介绍

据中国汽车工业协会数据&#xff0c;2023年我国汽车产销量分别达3016.1万辆和3009.4万辆&#xff0c;年产销量双双创历史新高&#xff0c;汽车行业进入了新时代。新汽车时代下的OEM竞争更激烈&#xff0c;汽车电子架构更复杂&#xff0c;研发周期更短&#xff0c;软件迭代更快&…

Cesium 问题:加载 gltf 格式的模型之后太小,如何让相机视角拉近

文章目录 问题分析问题 刚加载的模型太小,如何拉近视角放大 分析 在这里有两种方式进行拉近视角, 一种是点击复位进行视角拉近一种是刚加载就直接拉近视角// 模型三加载 this.damModel = new Cesium.Entity({name: "gltf模型",position:</

【AI大语言模型】ChatGPT在地学、GIS、气象、农业、生态、环境等领域中的应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

Maven私服搭建Nexus3

第一部分&#xff1a;仓库部署 下载地址&#xff1a;https://help.sonatype.com/en/download.html 备用下载链接&#xff0c;部分已经失效了 解压后会有两个文件夹&#xff1a; nexus-3.20.1-01 sonatype-work 访问地址配置路径 \nexus-3.20.1-01\bin\nexus.vmoptions -Xms1…

【广度优先搜索】【网格】【割点】【 推荐】1263. 推箱子

作者推荐 视频算法专题 涉及知识点 广度优先搜索 网格 割点 并集查找 LeetCode:1263. 推箱子 「推箱子」是一款风靡全球的益智小游戏&#xff0c;玩家需要将箱子推到仓库中的目标位置。 游戏地图用大小为 m x n 的网格 grid 表示&#xff0c;其中每个元素可以是墙、地板或…

Android13 针对low memory killer内存调优

引入概念 在旧版本的安卓系统中&#xff0c;当触发lmk&#xff08;low memory killer&#xff09;的时候一般认为就是内存不足导致&#xff0c;但是随着安卓版本的增加lmk的判断标准已经不仅仅是内存剩余大小&#xff0c;io&#xff0c;cpu同样会做评判&#xff0c;从而保证设备…

挑战杯 基于人工智能的图像分类算法研究与实现 - 深度学习卷积神经网络图像分类

文章目录 0 简介1 常用的分类网络介绍1.1 CNN1.2 VGG1.3 GoogleNet 2 图像分类部分代码实现2.1 环境依赖2.2 需要导入的包2.3 参数设置(路径&#xff0c;图像尺寸&#xff0c;数据集分割比例)2.4 从preprocessedFolder读取图片并返回numpy格式(便于在神经网络中训练)2.5 数据预…

阿里巴巴中国站获得淘口令真实url API(1688.item_password)

阿里巴巴&#xff08;1688.com&#xff09;是一个B2B电商平台&#xff0c;而淘口令&#xff08;或称为淘宝口令&#xff09;是一种在阿里巴巴集团旗下的淘宝和天猫平台中分享商品或活动链接的特殊形式。淘口令通常包含一串字符&#xff0c;用户可以复制这串字符并在淘宝或天猫的…

YOLOv5代码解读[02] models/yolov5l.yaml文件解析

文章目录 YOLOv5代码解读[02] models/yolov5l.yaml文件解析yolov5l.yaml文件检测头1--->耦合头检测头2--->解耦头检测头3--->ASFF检测头Model类解析parse_model函数 YOLOv5代码解读[02] models/yolov5l.yaml文件解析 yolov5l.yaml文件 # YOLOv5 &#x1f680; by Ult…

Maven 私服 Nexus3

一、Maven和Nexus3 简介 Maven是一个采用纯Java编写的开源项目管理工具&#xff0c;采用一种被称之为Project Object Model(POM)概念来管理项目&#xff0c;所有的项目配置信息都被定义在一个叫做POM.xml的文件中, 通过该文件Maven可以管理项目的整个生命周期&#xff0c;包括…

Unity xLua开发环境搭建与基础进阶

Unity是一款非常流行的游戏开发引擎&#xff0c;而xLua是一个为Unity开发者提供的Lua框架&#xff0c;可以让开发者使用Lua语言来进行游戏开发。在本文中&#xff0c;我们将介绍如何搭建Unity xLua开发环境&#xff0c;并进行基础进阶的学习。 环境搭建 首先&#xff0c;我们需…

华为算法题 go语言

1 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返…

Internet Download Manager 6.42.3 (IDM) 中文免激活绿色版

相信很多网友都遇到过一种情况&#xff0c;网页有些视频资源或者音频资源不知道如何下载&#xff0c;一直不知道如何解决&#xff0c;为此小编特意带来了这款&#xff1a;Internet Download Manager电脑版&#xff0c;这是一款非常专业且十分好用的下载工具&#xff0c;也就是大…

Mysql系列之命令行登录、连接工具登录、数据库表常用命令

登录与常用命令 连接工具登录命令行登录数据库1、查看数据库2、指定数据库3、查看当前数据库4、建库语句 数据表1、查看数据表2、查看表结构信息3、查看建表语句4、建表语句 连接工具登录 首先下载mysql连接工具&#xff0c;解压后直接打开软件&#xff0c;按以下步骤操作&…

单调栈总结以及Leetcode案例解读与复盘

单调栈总结以及Leetcode案例解读与复盘 一、单调栈是什么&#xff1f; 单调栈&#xff08;monotonous stack&#xff09;是指栈的内部从栈底到栈顶满足单调性的栈结构。 二、如何维护单调性 新元素入栈时&#xff0c;会与栈顶元素进行比较&#xff0c;使得栈始终保持单调性…

LInux-信号1

文章目录 前言一、信号是什么&#xff1f;二、学习步骤使用kill -l命令查看信号列表可以看到有那么多信号&#xff0c;那么进程是如何识别这么多信号的呢&#xff1f; 使用kill命令终止进程信号的捕捉kill函数raise函数abort函数 Core dump如何查看自己的核心转储功能是否被打开…

公司如何防止终端核心文件数据\资料外泄、泄漏?

如何防止电脑文件被拷贝&#xff1f; 防止电子文件泄密是一个重要的信息安全问题。 PC端地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是一些建议的措施&#xff1a; 加强员工教育和培训&#xff1a;提高员工对电子文…