測試報告 之 testNG + Velocity 編寫自定義html測試報告
阿新 • • 發佈:2018-11-27
之前用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開始時間排序
}
}