之前用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)】
百度网盘贡献路径如下:
链接: 密码: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 CollectionexcludeTestsMethod; //未执行的测试方法 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 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 getOutput() { return output; } public void setOutput(List 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 excludeTestsMethod) { this.excludeTestsMethod = excludeTestsMethod; } public Collection 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(ListxmlSuites, List 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 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 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
Test Report IKEA Web Automatic Test
OverView........ | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
all | excluded | passed | faild | skipped | StartTime(S) | EndTime(S) | duration(S) | passration | alltestMethod | excluedMethod | |
TestResult | $overView.allTestsSize | $overView.excludeTestsSize | $overView.passedTestsSize | $overView.failedTestsSize | $overView.skippedTestsSize | $overView.starttime | $overView.endtime | $overView.testsTime | $overView.passPercent | #foreach($p in $overView.allTestsMethod) $p #end | #foreach($e in $overView.excludeTestsMethod) $e #end |
PassTests............. | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
testName | className | starttime | endtime | duration | params | description | output | dependMethod | |||
$velocityCount | ${p.testName} #if(${p.description}) (${p.description}) #end | $p.className | $p.starttime | $p.endtime | $p.duration | $!p.params | $!p.description | #foreach($o in $p.output) $o #end | $p.dependMethod | $!p.throwable | #if($p.throwable ) #foreach($o in $p.stackTrace) $o #end #end | #end
FailedTests............... | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
testName | className | StartTime | EndTime | duration | params | description | output | dependMethod | throwable | stackTrace | |
$velocityCount | $p.testName | $p.className | $p.starttime | $p.endtime | $p.duration | $!p.params | $!p.description | #foreach($o in $p.output) $o #end | $p.dependMethod | $p.throwable | #if($p.throwable ) #foreach($o in $p.stackTrace) $o #end #end | #end
ReporterData
package main.java.baseReport;import org.testng.*;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.*;public class ReporterData { // 测试结果Set转为list,再按执行时间排序 ,返回list public List sortByTime(Set str) { List list = new ArrayList (); 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 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 testResults(IResultMap map, int status) { // 测试结果详细数据 List list = new ArrayList (); 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(); Listlist = new ArrayList (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 list = new ArrayList (stackTrace.length); for (Object o:stackTrace){ list.add(renderArgument(o)); } return commaSeparate(list); } /** * 获取全部日志输出信息 * @return */ public List getAllOutput(){ return Reporter.getOutput(); } /** * 按testresult获取日志输出信息 * @param result * @return */ public List 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 strings) { StringBuilder buffer = new StringBuilder(); Iterator 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{ 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显示的报表,目前是一个测试类出一个报表,很尴尬。
只显示了最后一个测试类,并且覆盖了前面的