之前用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)】
百度网盘贡献路径如下:
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 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 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(List xmlSuites, 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 ReportIKEA 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 |
#foreach( $p in $pass)
$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 |
#foreach( $p in $fail)
$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();
List list = 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显示的报表,目前是一个测试类出一个报表,很尴尬。
只显示了最后一个测试类,并且覆盖了前面的