【转】

https://testerhome.com/topics/3487

【参考】https://www.cnblogs.com/cheese320/p/8890929.html  做了些修改,换了模板引擎,换为freemaker。

在test用例中增加注解,或者在testng.xml中添加监听器配置

//<listener class-name="report.GenerateReporter" /> 或者testng.xml加入
@Listeners({report.GenerateReporter.class})

目录结构:

代码:

package report;

import java.util.Arrays;
import java.util.Collection;
import java.util.List; import org.testng.ITestNGMethod; public class DataBean {
private int excludeTestsSize; //未执行的test数量
private int passedTestsSize; //测试通过的数量
private int failedTestsSize; //测试失败的数量
private int skippedTestsSize; //测试跳过的数量
private int allTestsSize; //全部执行的测试的数量
private ITestNGMethod[] allTestsMethod; //全部执行的测试方法
private Collection<ITestNGMethod> excludeTestsMethod; //未执行的测试方法
private String testsTime; //测试耗时
private String passPercent; //测试通过率
private String testName; //测试方法名
private String className; //测试类名
private String duration; //单个测试周期
private String params; //测试用参数
private String description; //测试描述
private List<String> output; //Reporter Output
private String dependMethod; //测试依赖方法
private Throwable throwable; //测试异常原因
private StackTraceElement[] stackTrace; // 异常堆栈信息 public int getExcludeTestsSize() {
return excludeTestsSize;
} public void setExcludeTestsSize(int excludeTestsSize) {
this.excludeTestsSize = excludeTestsSize;
} public int getPassedTestsSize() {
return passedTestsSize;
} public void setPassedTestsSize(int passedTestsSize) {
this.passedTestsSize = passedTestsSize;
} public int getFailedTestsSize() {
return failedTestsSize;
} public void setFailedTestsSize(int failedTestsSize) {
this.failedTestsSize = failedTestsSize;
} public int getSkippedTestsSize() {
return skippedTestsSize;
} public void setSkippedTestsSize(int skippedTestsSize) {
this.skippedTestsSize = skippedTestsSize;
} public int getAllTestsSize() {
return allTestsSize;
} public void setAllTestsSize(int allTestsSize) {
this.allTestsSize = allTestsSize;
} public String getPassPercent() {
return passPercent;
} public void setPassPercent(String passPercent) {
this.passPercent = passPercent;
} public String getTestName() {
return testName;
} public void setTestName(String testName) {
this.testName = testName;
} public String getClassName() {
return className;
} public void setClassName(String className) {
this.className = className;
} public String getDuration() {
return duration;
} public void setDuration(String duration) {
this.duration = duration;
} public String getParams() {
return params;
} public void setParams(String params) {
this.params = params;
} public String getDescription() {
return description;
} public void setDescription(String description) {
this.description = description;
} public List<String> getOutput() {
return output;
} public void setOutput(List<String> output) {
this.output = output;
} public String getDependMethod() {
return dependMethod;
} public void setDependMethod(String dependMethod) {
this.dependMethod = dependMethod;
} public Throwable getThrowable() {
return throwable;
} public void setThrowable(Throwable throwable2) {
this.throwable = throwable2;
} public StackTraceElement[] getStackTrace() {
return stackTrace;
} public void setStackTrace(StackTraceElement[] stackTrace) {
this.stackTrace = stackTrace;
} public void setTestsTime(String testsTime) {
this.testsTime = testsTime;
} public String getTestsTime() {
return testsTime;
} public void setAllTestsMethod(ITestNGMethod[] allTestsMethod) {
this.allTestsMethod = allTestsMethod;
} public ITestNGMethod[] getAllTestsMethod() {
return allTestsMethod;
} public void setExcludeTestsMethod(Collection<ITestNGMethod> excludeTestsMethod) {
this.excludeTestsMethod = excludeTestsMethod;
} public Collection<ITestNGMethod> getExcludeTestsMethod() {
return excludeTestsMethod;
} @Override
public String toString() {
return "DataBean{" +
"excludeTestsSize=" + excludeTestsSize +
", passedTestsSize=" + passedTestsSize +
", failedTestsSize=" + failedTestsSize +
", skippedTestsSize=" + skippedTestsSize +
", allTestsSize=" + allTestsSize +
", allTestsMethod=" + Arrays.toString(allTestsMethod) +
", excludeTestsMethod=" + excludeTestsMethod +
", testsTime='" + testsTime + '\'' +
", passPercent='" + passPercent + '\'' +
", testName='" + testName + '\'' +
", className='" + className + '\'' +
", duration='" + duration + '\'' +
", params='" + params + '\'' +
", description='" + description + '\'' +
", output=" + output +
", dependMethod='" + dependMethod + '\'' +
", throwable=" + throwable +
", stackTrace=" + Arrays.toString(stackTrace) +
'}';
}
}
package report;

import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import org.testng.IReporter;
import org.testng.IResultMap;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.xml.XmlSuite;
import freemarker.template.*; public class GenerateReporter implements IReporter {
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
String outputDirectory) {
// TODO Auto-generated method stub
try {
//freemaker的配置
Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
cfg.setClassForTemplateLoading(this.getClass(),"/templates");
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
//freemaker的模板文件
Template temp = cfg.getTemplate("overview.ftl"); Map context = new HashMap(); for (ISuite suite : suites) {
Map<String, ISuiteResult> suiteResults = suite.getResults();
for (ISuiteResult suiteResult : suiteResults.values()) {
ReporterData data = new ReporterData();
ITestContext testContext = suiteResult.getTestContext();
// 把数据填入上下文
context.put("overView", data.testContext(testContext));//测试结果汇总信息
//ITestNGMethod[] allTests = testContext.getAllTestMethods();//所有的测试方法
//Collection<ITestNGMethod> excludeTests = testContext.getExcludedMethods();//未执行的测试方法
IResultMap passedTests = testContext.getPassedTests();//测试通过的测试方法
IResultMap failedTests = testContext.getFailedTests();//测试失败的测试方法
IResultMap skippedTests = testContext.getSkippedTests();//测试跳过的测试方法 context.put("pass", data.testResults(passedTests, ITestResult.SUCCESS));
context.put("fail", data.testResults(failedTests, ITestResult.FAILURE));
context.put("skip", data.testResults(skippedTests, ITestResult.FAILURE)); }
}
System.out.println(context.get("overView").toString());
// 输出流
//Writer writer = new BufferedWriter(new FileWriter("report.html"));
OutputStream out=new FileOutputStream("target/report.html");
Writer writer = new BufferedWriter(new OutputStreamWriter(out,"utf-8"));//解决乱码问题
// 转换输出
temp.process(context,writer);
writer.flush();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
package report;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set; import org.testng.IResultMap;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.Reporter; public class ReporterData {
// 测试结果Set<ITestResult>转为list,再按执行时间排序 ,返回list
public List<ITestResult> sortByTime(Set<ITestResult> str) {
List<ITestResult> list = new ArrayList<ITestResult>();
for (ITestResult r : str) {
list.add(r);
}
Collections.sort(list);
return list; } public DataBean testContext(ITestContext context) {
// 测试结果汇总数据
DataBean data = new DataBean();
ReportUnits units = new ReportUnits();
IResultMap passedTests = context.getPassedTests();
IResultMap failedTests= context.getFailedTests();
IResultMap skipedTests = context.getSkippedTests();
//全部测试周期方法,包括beforetest,beforeclass,beforemethod,aftertest,afterclass,aftermethod
//IResultMap passedConfigurations =context.getPassedConfigurations();
//IResultMap failedConfigurations =context.getFailedConfigurations();
//IResultMap skipedConfigurations =context.getSkippedConfigurations();
Collection<ITestNGMethod> excludeTests = context.getExcludedMethods(); int passedTestsSize = passedTests.size();
int failedTestsSize = failedTests.size();
int skipedTestsSize = skipedTests.size();
int excludeTestsSize = excludeTests.size();
//所有测试结果的数量=测试pass+fail+skip的和,因为数据驱动一个测试方法有多次执行的可能,导致方法总数并不等于测试总数
int allTestsSize= passedTestsSize+failedTestsSize+skipedTestsSize;
data.setAllTestsSize(allTestsSize);
data.setPassedTestsSize(passedTestsSize);
data.setFailedTestsSize(failedTestsSize);
data.setSkippedTestsSize(skipedTestsSize);
data.setExcludeTestsSize(excludeTestsSize);
data.setTestsTime(units.getTestDuration(context));
data.setPassPercent(units.formatPercentage(passedTestsSize, allTestsSize));
data.setAllTestsMethod(context.getAllTestMethods());
data.setExcludeTestsMethod(context.getExcludedMethods()); return data; } public List<DataBean> testResults(IResultMap map, int status) {
// 测试结果详细数据
List<DataBean> list = new ArrayList<DataBean>();
ReportUnits units = new ReportUnits();
map.getAllResults().size();
for (ITestResult result : sortByTime(map.getAllResults())) {
DataBean data = new DataBean();
data.setTestName(result.getName());
data.setClassName(result.getTestClass().getName());
data.setDuration(units.formatDuration(result.getEndMillis()
- result.getStartMillis()));
data.setParams(units.getParams(result));
data.setDescription(result.getMethod().getDescription());
data.setOutput(Reporter.getOutput(result));
data.setDependMethod(units.getDependMethods(result));
data.setThrowable(result.getThrowable());
if (result.getThrowable() != null) {
data.setStackTrace(result.getThrowable().getStackTrace());
}
list.add(data);
}
return list;
} }
package report;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.Reporter; public class ReportUnits {
private static final NumberFormat DURATION_FORMAT = new DecimalFormat("#0.000");
private static final NumberFormat PERCENTAGE_FORMAT = new DecimalFormat("#0.00%");
/**
*测试消耗时长
*return 秒,保留3位小数
*/
public String getTestDuration(ITestContext context){
long duration;
duration=context.getEndDate().getTime()-context.getStartDate().getTime();
return formatDuration(duration);
} public String formatDuration(long elapsed)
{
double seconds = (double) elapsed / 1000;
return DURATION_FORMAT.format(seconds);
}
/**
*测试通过率
*return 2.22%,保留2位小数
*/
public String formatPercentage(int numerator, int denominator)
{
return PERCENTAGE_FORMAT.format(numerator / (double) denominator);
} /**
* 获取方法参数,以逗号分隔
* @param result
* @return
*/
public String getParams(ITestResult result){
Object[] params = result.getParameters();
List<String> list = new ArrayList<String>(params.length);
for (Object o:params){
list.add(renderArgument(o));
}
return commaSeparate(list);
}
/**
* 获取依赖的方法
* @param result
* @return
*/
public String getDependMethods(ITestResult result){
String[] methods=result.getMethod().getMethodsDependedUpon();
return commaSeparate(Arrays.asList(methods));
}
/**
* 堆栈轨迹,暂不确定怎么做,放着先
* @param throwable
* @return
*/
public String getCause(Throwable throwable){
StackTraceElement[] stackTrace=throwable.getStackTrace(); //堆栈轨迹
List<String> list = new ArrayList<String>(stackTrace.length);
for (Object o:stackTrace){
list.add(renderArgument(o));
}
return commaSeparate(list);
}
/**
* 获取全部日志输出信息
* @return
*/
public List<String> getAllOutput(){
return Reporter.getOutput();
} /**
* 按testresult获取日志输出信息
* @param result
* @return
*/
public List<String> getTestOutput(ITestResult result){
return Reporter.getOutput(result);
} /*将object 转换为String*/
private String renderArgument(Object argument)
{
if (argument == null)
{
return "null";
}
else if (argument instanceof String)
{
return "\"" + argument + "\"";
}
else if (argument instanceof Character)
{
return "\'" + argument + "\'";
}
else
{
return argument.toString();
}
}
/*将集合转换为以逗号分隔的字符串*/
private String commaSeparate(Collection<String> strings)
{
StringBuilder buffer = new StringBuilder();
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext())
{
String string = iterator.next();
buffer.append(string);
if (iterator.hasNext())
{
buffer.append(", ");
}
}
return buffer.toString();
}
}
package report;

import org.testng.ITestResult;

public class TestResultSort implements Comparable<ITestResult> {
private Long order;
@Override
public int compareTo(ITestResult arg0) {
// TODO Auto-generated method stub
return this.order.compareTo( arg0.getStartMillis());//按test开始时间排序
} }

完美好看的html报告freemaker模板。css样式在foxmail等客户端不生效时,可能是客户端不支持class样式,直接将style="..."写到元素里。

<?xml version="1.0" encoding="utf-8" ?>

<head>
<title>test</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="description" content="TestNG unit test results." />
<style type="text/css">
body
{
margin: 10px 20px;
font-size: 14px;
font-family: "Arial","Microsoft YaHei","黑体","宋体",sans-serif;
}
/*table*/
/*{*/
/*border-collapse: collapse;*/
/*text-align: center;*/
/*font-size: 14px;*/
/*}*/
/*table td, table th*/
/*{*/
/*border: 2px solid #cc6f4a;*/
/*color: #666;*/
/*height: 20px;*/
/*text-align: center;*/
/*padding: 3px 3px;*/
/*}*/
/*table thead th*/
/*{*/
/**/
/*width: 100px;*/
/*}*/
/*table tr:nth-child(odd)*/
/*{*/
/*background: #fff;*/
/*}*/
/*table tr:nth-child(even)*/
/*{*/
/*background: #c9dafa;*/
/*}*/
.successBtn {
width: 60px;
padding:3px;
background-color: #58ab48;
border-color: #58ab48;
color: #fff;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px; /* future proofing */
-khtml-border-radius: 10px; /* for old Konqueror browsers */
text-align: center;
vertical-align: middle;
border: 1px solid transparent;
font-weight: 500;
/*font-size:125%*/
}
.failBtn{
width: 60px;
padding:3px;
background-color: #ab2e2d;
border-color: #ab2e2d;
color: #fff;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px; /* future proofing */
-khtml-border-radius: 10px; /* for old Konqueror browsers */
text-align: center;
vertical-align: middle;
border: 1px solid transparent;
font-weight: 500;
/*font-size:125%*/
}
</style> <style>
/* Border styles */
.tabNoBorder thead, .tabNoBorder tr {
border-top-width: 1px;
border-top-style: solid;
border-top-color: rgb(211, 202, 221);
}
.tabNoBorder {
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: rgb(211, 202, 221);
} /* Padding and font style */
.tabNoBorder td, .tabNoBorder th {
padding: 5px 10px;
font-size: 14px;
font-family: Verdana;
color: rgb(95, 74, 121);
} /* Alternating background colors */
.tabNoBorder tr:nth-child(even) {
background: rgb(223, 216, 232)
}
.tabNoBorder tr:nth-child(odd) {
background: #FFF
}
</style> </head>
<body>
<br/>
<h2>Summary</h2>
<table id="summary" class="tabNoBorder">
<tr class="columnHeadings">
<th>用例总数</th>
<th>未执行用例数</th>
<th>执行通过</th>
<th>执行失败</th>
<th>跳过用例数</th>
<th>执行时间(s)</th>
<th>用例通过率</th>
<#--<th>alltestMethod</th>-->
<#--<th>excluedMethod</th>-->
</tr> <tr>
<td>${overView.allTestsSize}</td>
<td>${overView.excludeTestsSize}</td>
<td>${overView.passedTestsSize}</td>
<td>${overView.failedTestsSize}</td>
<td>${overView.skippedTestsSize}</td>
<td>${overView.testsTime}</td>
<td>${overView.passPercent}</td>
<#--<td>-->
<#--<#list overView.allTestsMethod as item>-->
<#--${item.methodName}-->
<#--</#list>-->
<#--</td>-->
<#--<td>-->
<#--<#list overView.excludeTestsMethod as item1>-->
<#--${item1.methodName}-->
<#--</#list>-->
<#--</td>-->
</tr>
</table>
<br/><br/>
<h2>Detail</h2>
<table class="tabNoBorder">
<tr class="columnHeadings">
<th>编号</th>
<th>Class</th>
<th>MethodName</th>
<th>用例描述</th>
<th>执行结果</th>
<th>执行时间(s)</th>
<th>报错信息</th>
</tr>
<#assign caseNo = 0>
<#list fail as failCase>
<tr>
<#assign caseNo=caseNo+1>
<td>${caseNo}</td>
<td>${failCase.className}</td>
<td>${failCase.testName}</td>
<td>${failCase.description!}</td>
<td><div class="failBtn">Fail</div></td>
<td>${failCase.duration!}</td>
<td>${failCase.throwable!}</td>
</tr>
</#list>
<#list pass as passCase>
<tr>
<#assign caseNo=caseNo+1>
<td>${caseNo}</td>
<td>${passCase.className}</td>
<td>${passCase.testName}</td>
<td>${passCase.description!}</td>
<td><div class="successBtn">Success</div></td>
<td>${passCase.duration!}</td>
<td>${passCase.throwable!}</td>
</tr>
</#list> </table> <br/><br/> </body>
</html>

testng自定义html报告,根据freemaker生成的更多相关文章

  1. testng日志和报告

    TestNG是通过 Listeners 或者 Reporters 生成测试报告. Listeners,即 org.testng.ITestListener 的实现,能够在测试执行过程中发出各种测试结果 ...

  2. [唐胡璐]Selenium技巧- ReportNG替换TestNG默认结果报告

    TestNG默认的报告虽然内容挺全,但是展现效果却不太理想,不易阅读。因此我们想利用ReportNG来替代TestNG默认的report。 什么是ReportNG呢?这里不多说,请直接参见:http: ...

  3. 《手把手教你》系列基础篇(八十五)-java+ selenium自动化测试-框架设计基础-TestNG自定义日志-下篇(详解教程)

    1.简介 TestNG为日志记录和报告提供的不同选项.现在,宏哥讲解分享如何开始使用它们.首先,我们将编写一个示例程序,在该程序中我们将使用 ITestListener方法进行日志记录. 2.Test ...

  4. Font Combiner – 自定义网页字体和图标生成工具

    Font Combiner 是一个功能丰富的 Web 字体生成工具和字体改进工具,提供字距调整.构造子集.各种提示选项和自定义字体字形组合.您可以生成您自己的自定义字体的格式和文件大小. 另外还有成千 ...

  5. Freemaker生成复杂样式图片并无文件损坏的excel

    Freemaker生成复杂样式图片并无文件损坏的excel 参考Freemarker整合poi导出带有图片的Excel教程,优化代码实现 功能介绍:1.支持Freemarker导出Excel的所有功能 ...

  6. testng生成自定义html报告

    转自:https://blog.csdn.net/kdslkd/article/details/51198433 testng原生的或reportng的报告总有些不符合需要,尝试生成自定义测试报告,用 ...

  7. TestNG之测试执行后没有生成默认测试报告(IDEA)

    使用IDEA+TestNG进行测试,没有生成 测试报告,是因为没有勾选监听器使用默认报告,具体操作如下: “Run” -> "Edit Configurations" -&g ...

  8. XsdGen:通过自定义Attribute与反射自动生成XSD

    前言 系统之间的数据交互往往需要事先定义一些契约,在WCF中我们需要先编写XSD文件,然后通过自动代码生成工具自动生成C#对象.对于刚刚接触契约的人来说,掌握XMLSpy之类的软件之后确实比手写XML ...

  9. 转: Oracle AWR 报告 每天自动生成并发送邮箱

    原贴地址:http://www.cnblogs.com/vigarbuaa/archive/2012/09/05/2671794.html Oracle AWR 介绍http://blog.csdn. ...

随机推荐

  1. MATLAB批量修改图片名称

    申明:转载请注明出处. 设在“D:\UserDesktop\pic\”目录下有很多张格式为jpg照片,命名不规则,如图. 现在用MATLAB批量修改所有图片的命名格式,改为1.jpg,2.jpg,.. ...

  2. jquery 不支持$.browser

    if (!$.browser) { $.browser = { mozilla : /firefox/.test(navigator.userAgent.toLowerCase()), webkit ...

  3. java POI技术之导出数据优化(15万条数据1分多钟)

    专针对导出excel2007 ,用到poi3.9的jar package com.cares.ynt.util; import java.io.File; import java.io.FileOut ...

  4. SC || 关于java迭代中修改迭代集合的操作

    在通过for循环遍历整个List/Map等的时候,如果想要进行remove的操作,这时就更改了迭代集合,会出现错误 一种方法是如果只会remove一个可以remove后直接break 另一种是把集合先 ...

  5. hibernate4整合spring3.1的过程中的异常问题

    (1)hibernate4整合spring3.1的过程中,发现了java.lang.NoClassDefFoundError: Lorg/hibernate/cache/CacheProvider异常 ...

  6. Luogu P2123 皇后游戏(贪心)

    题目链接:P2123 皇后游戏 如果证明这个题为什么是贪心的话,我是不会的,但是一看这个题目就是一个贪心,然后满足贪心的性质: 都能从两个人(东西)扩展到n个人(东西) 一定能从相邻状态扩展到不相邻的 ...

  7. IE6,7,8,9还有火狐浏览器的兼容

    /*FF.Opear等支持Web标准的浏览器*/#header {        margin-top: 23px;        margin-bottom: 23px;}/*IE6浏览器*/*ht ...

  8. 《UNIX环境高级编程》笔记——4.文件和目录

    一.引言 本章描述文件系统的其他特征和文件的性质.有些背景知识需要注意,例如用户ID与文件权限.文件系统等. 二.函数stat.fstat.fstatat和lstat #include <sys ...

  9. Python之路-迭代器 生成器 推导式

    迭代器 可迭代对象 遵守可迭代协议的就是可迭代对象,例如:字符串,list dic tuple set都是可迭代对象 或者说,能被for循环的都是可迭代对象 或者说,具有对象.__iter__方法的都 ...

  10. (转) 改变UITextField placeHolder颜色、字体 、输入光标位置等

    我们有时需要定制化UITextField对象的风格,可以添加许多不同的重写方法,来改变文本字段的显示行为.这些方法都会返回一个CGRect结构,制定了文本字段每个部件的边界范围,甚至修改placeHo ...