之前用testNG自带的test-outputemailable-report.html,做出的UI自动化测试报告,页面不太好看。
在网上找到一个新的报告编写,自己尝试了一下,埋了一些坑,修改了输出时间格式,最终出的结果比以前稍好。
简单介绍下Velocity
1.不用像jsp那样编译成servlet(.Class)文件,直接装载后就可以运行了,装载的过程在web.xml里面配置。【后缀名为.vhtml是我们自己的命名方式。也只有在这里配置了哪种类型的文件,那么这种类型的文件才能解析velocity语法】
2.web页面上可以很方便的调用java后台的方法,不管方法是静态的还是非静态的。只需要在toolbox.xml里面把类配置进去就可以咯。【调用的方法 $class.method()】即可。
3.可以使用模版生成静态文档html【特殊情况下才用】
需要下载两个war包,
testng-6.9.9.jar,velocity-1.7.jar【亲测之后发现velocity-1.7.jar会报错,建议用velocity-dep-1.4.jar,因为后者包含了三个war包的内容(commons-collections-3.2.1.jar、commons-lang-2.4.jar和oro-2.0.8.jar)】
百度网盘贡献路径如下:
链接:https://pan.baidu.com/s/1FeN-di2T_nMo3ahHQ91xtg 密码:2nb7
DataBean.java
package main.java.baseReport; import org.testng.ITestNGMethod; import java.util.Collection; import java.util.List; 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 starttime; // private String endtime; // 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 String getStarttime() { return starttime; } public void setStarttime(String starttime) { this.starttime = starttime; } public String getEndtime() { return endtime; } public void setEndtime(String endtime) { this.endtime = endtime; } 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; } }
GenerateReporter
package main.java.baseReport; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.testng.*; import org.testng.xml.XmlSuite; import java.io.*; import java.util.List; import java.util.Map; import java.util.Properties; import static java.lang.System.out; public class GenerateReporter implements IReporter { @Override public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) { // TODO Auto-generated method stub try { // 初始化并取得Velocity引擎 VelocityEngine ve = new VelocityEngine(); Properties p = new Properties(); //虽然不懂为什么这样设置,但结果是好的.可以用了 p.setProperty("resource.loader", "class"); p.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); ve.init(p); Template t = ve.getTemplate("main/java/baseReport/overview.vm"); VelocityContext context = new VelocityContext(); 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();//测试跳过的测试方法 //IResultMap starttime=testContext.getStartDate(); //IResultMap endtime=testContext.getEndDate(); context.put("pass", data.testResults(passedTests, ITestResult.SUCCESS)); context.put("fail", data.testResults(failedTests, ITestResult.FAILURE)); context.put("skip", data.testResults(skippedTests, ITestResult.FAILURE)); } } // 输出流 OutputStream out = new FileOutputStream("report.html"); Writer writer = new BufferedWriter(new OutputStreamWriter(out, "utf-8"));//解决乱码问题 // 转换输出 t.merge(context, writer); //System.out.println(writer.toString()); writer.flush(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Overview.vm
<?xml version="1.0" encoding="utf-8" ?> <head> <title>Test Report</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <meta name="description" content="TestNG unit test results." /> </head> <body> <h1>IKEA Web Automatic Test</h1> <table border="1"> <tr> <th>OverView........</th> <th colspan="6" class="header suite"> <div > <a href="http://www.baidu.com">Detail Information for test cases</a> </div> </th> </tr> <tr class="columnHeadings"> <td> </td> <th>all</th> <th>excluded</th> <th>passed</th> <th>faild</th> <th>skipped</th> <th>StartTime(S)</th> <th>EndTime(S)</th> <th>duration(S)</th> <th>passration</th> <th>alltestMethod</th> <th>excluedMethod</th> </tr> <tr> <td>TestResult</td> <td>$overView.allTestsSize</td> <td>$overView.excludeTestsSize</td> <td>$overView.passedTestsSize</td> <td>$overView.failedTestsSize</td> <td>$overView.skippedTestsSize</td> <td>$overView.starttime</td> <td>$overView.endtime</td> <td>$overView.testsTime</td> <td>$overView.passPercent</td> <td> #foreach($p in $overView.allTestsMethod) $p<br/> #end </td> <td> #foreach($e in $overView.excludeTestsMethod) $e<br/> #end </td> </tr> </table> <br/><br/> <table border="1"> <tr> <th>PassTests.............</th> <th colspan="6" class="header suite"> <div > <a href="http://www.baidu.com">Detail Information for test cases</a> </div> </th> </tr> <tr class="columnHeadings"> <td> </td> <th>testName</th> <th>className</th> <th>starttime</th> <th>endtime</th> <th>duration</th> <th>params</th> <th>description</th> <th>output</th> <th>dependMethod</th> </tr> #foreach( $p in $pass) <tr> <td>$velocityCount</td> <td>${p.testName} #if(${p.description}) (${p.description}) #end</td> <td>$p.className</td> <td>$p.starttime</td> <td>$p.endtime</td> <td>$p.duration</td> <td>$!p.params</td> <td>$!p.description</td> <td> #foreach($o in $p.output) $o<br/> #end </td> <td>$p.dependMethod</td> <td>$!p.throwable</td> <td> #if($p.throwable ) #foreach($o in $p.stackTrace) $o<br/> #end #end </td> #end </tr> </table> <br/> <br/><br/> <table border="1"> <tr> <th>FailedTests...............</th> <th colspan="6" class="header suite"> <div > <a href="http://www.baidu.com">Detail Information for test cases</a> </div> </th> </tr> <tr class="columnHeadings"> <td> </td> <th>testName</th> <th>className</th> <th>StartTime</th> <th>EndTime</th> <th>duration</th> <th>params</th> <th>description</th> <th>output</th> <th>dependMethod</th> <th>throwable</th> <th>stackTrace</th> </tr> #foreach( $p in $fail) <tr> <td>$velocityCount</td> <td>$p.testName</td> <td>$p.className</td> <td>$p.starttime</td> <td>$p.endtime</td> <td>$p.duration</td> <td>$!p.params</td> <td>$!p.description</td> <td> #foreach($o in $p.output) $o<br/> #end </td> <td>$p.dependMethod</td> <td>$p.throwable</td> <td> #if($p.throwable ) #foreach($o in $p.stackTrace) $o<br/> #end #end </td> #end </tr> </table> <br/><br/> </body>
ReporterData
package main.java.baseReport; import org.testng.*; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; 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) throws ParseException { // 测试结果汇总数据 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.setStarttime(units.getStarttime(context)); data.setEndtime(units.getEndTime(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(); SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd HH:mm:ss"); 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.setStarttime(formatter.format(result.getEndMillis())); data.setEndtime(formatter.format(result.getEndMillis())); 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; } }
ReportUnits
package main.java.baseReport; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; 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%"); SimpleDateFormat formatter = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); /** *测试消耗时长 *return 秒,保留3位小数 */ public String getTestDuration(ITestContext context){ long duration; duration=context.getEndDate().getTime()-context.getStartDate().getTime(); return formatDuration(duration); } public String getStarttime(ITestContext context) throws ParseException { return formatter.format(context.getStartDate()); } public String getEndTime(ITestContext context){ return formatter.format(context.getEndDate()); //return context.getStartDate().toString(); } 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(); } }
TestResultSort
package main.java.baseReport; 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开始时间排序 } }
优缺点比较:
1. 多个测试类一起运行,通过配置testng.xml 一起运行的时候,testNG自带的eport会将多个测试文件显示在同一个报表中,并且根据测试类进行分门别类;自己编写的velocity显示的报表,目前是一个测试类出一个报表,很尴尬。
只显示了最后一个测试类,并且覆盖了前面的
原文地址:https://www.cnblogs.com/qianjinyan/p/9437354.html