之前用testNG自带的test-outputemailable-report.html,做出的UI自动化测试报告,页面不太好看。

测试报告 之 testNG + Velocity 编写自定义html测试报告_java


在网上找到一个新的报告编写,自己尝试了一下,埋了一些坑,修改了输出时间格式,最终出的结果比以前稍好。

测试报告 之 testNG + Velocity 编写自定义html测试报告_测试方法_02

简单介绍下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

 

 测试报告 之 testNG + Velocity 编写自定义html测试报告_sed_03

 

 

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显示的报表,目前是一个测试类出一个报表,很尴尬。

测试报告 之 testNG + Velocity 编写自定义html测试报告_java_04

 只显示了最后一个测试类,并且覆盖了前面的

测试报告 之 testNG + Velocity 编写自定义html测试报告_测试方法_05

 

测试报告 之 testNG + Velocity 编写自定义html测试报告_java_06 ------------------------- A little Progress a day makes you a big success... ----------------------------