1. 程式人生 > >測試報告 之 testNG + Velocity 編寫自定義html測試報告

測試報告 之 testNG + Velocity 編寫自定義html測試報告

之前用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開始時間排序
    }
}