【转】
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模板
<?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>