测试报告 之 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开始时间排序
    }
}

  

优缺点比较:

1. 多个测试类一起运行,通过配置testng.xml 一起运行的时候,testNG自带的eport会将多个测试文件显示在同一个报表中,并且根据测试类进行分门别类;自己编写的velocity显示的报表,目前是一个测试类出一个报表,很尴尬。

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

原文地址:https://www.cnblogs.com/qianjinyan/p/9437354.html

时间: 2024-10-13 03:08:11

测试报告 之 testNG + Velocity 编写自定义html测试报告的相关文章

【测试报告】-TestNG自定义记录器

在本节中,我们将介绍一个例子,编写自定义记录器和TestNG的方法.要编写一个定制的记录器类,我们的扩展类应实现IReporter接口.让我们继续前进,并创建一个示例使用自定义的记录器. 创建测试案例类 创建一个Java类为 SampleTest.java 在 C:\ > TestNG_WORKSPACE import org.testng.Assert; import org.testng.annotations.Test; public class SampleTest { @Test pu

SpringBoot编写自定义的starter

在之前的文章中,我们分析过SpringBoot内部的自动化配置原理和自动化配置注解开关原理. 我们先简单分析一下mybatis starter的编写,然后再编写自定义的starter. mybatis中的autoconfigure模块中使用了一个叫做MybatisAutoConfiguration的自动化配置类. 这个MybatisAutoConfiguration需要在这些Condition条件下才会执行: @ConditionalOnClass({ SqlSessionFactory.cla

用mel编写自定义节点的属性编辑器界面

用mel编写自定义节点的属性编辑器界面比较麻烦,而且网上例子又少,下面给出一个范例,说明基本的格式 // 初始化节点时调用 global proc initControl(string $attrName) { // 传进来的参数是节点属性名,使用这个方法获得节点名称 string $nodeName = `plugNode $attrName`; setUITemplate -pst "attributeEditorTemplate"; button -label "Com

学iOS开发(二)——实现一款App之编写自定义类

今天学iOS开发(二)--实现一款App之编写自定义类 当开发iOS应用程序时,你会发现在许多场景下,你需要编写自己的自定义类.当你需要数据和自定义行为一起打包时,自定义类就很有用了.在一个自定义的类中,你可以为存储.操纵和显示数据定义你自己的行为. 例如,考虑iOS Clock app中的全球时钟标签(World Clock tab).这个表视图中的单元格需要显示比标准表视图单元格更多的内容.这是一个不错的机会实现一个扩展UITableViewCell行为的子类,从而在给定的表视图单元格中显示

(译)Getting Started——1.3.4 Writing a Custom Class(编写自定义的类)

 在开发IOS应用中,当你编写自定义的类时,你会发现很多的特殊场合.当你需要把自定义的行为和数据包装在一起时,自定义的类非常有用.在自定义的类中,你可以定义自己的存储.处理和显示数据的方法. 例如,IOS Clock应用中的World Clock面板.table view中的单元格需要显示比标准table view单元格更多的显示内容.这就是一个很好的机会来实现一个继承了UITableViewCell类的子类,用于在给定的table view单元格中显示更多的自定义内容.如果你正在设计自定义的类

在 WF 4 中编写自定义控制流活动

在 WF 4 中编写自定义控制流活动 Leon Welicki 控制流是指组织和执行程序中各个指令的方法. 在 Windows Workflow Foundation 4 (WF 4) 中,控制流活动掌控了一个或多个子活动的执行语义. WF 4 活动工具箱中的示例包括:Sequence.Parallel.If.ForEach.Pick.Flowchart 和 Switch 等等. WF 运行时对 Sequence 或 Parallel 等控制流并不很了解. 从它的角度看来,一切都只是活动而已. 

Android Cordova 插件开发之编写自定义插件

前言 本文适合Android+web的复合型人才,因为cordova本身就是混合开发,所以在Android开发的基础上,还要懂web相关技术(HTML+CSS+JS),但是也有例外,比如我,只需负责Android方面,web方面的交由其他web组人员开发.虽然,web略懂一点,但我主要还是搞Android开发的. 编写自定义插件类 本节的内容是,自定义一个dialog插件,供web调用,显示系统弹窗. 新建一个包名,我这里使用org.apache.cordova.dialog,然后创建个类Cus

编写自定义标签

1.8 编写自定义标签 一.作用: 自定义标签主要用于移除Jsp页面中的java代码. ?   控制jsp页面某一部分内容是否执行. ?   控制整个jsp页面是否执行. ?   控制jsp页面内容重复执行. ?   修改j页面内容输出. 二.标签体系:传统标签和简单标签: 1. JspTag接口 JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属性和方法.JspTag接口有Tag和SimpleTag两个直接子接口,JSP2.0以前的版本中只有Tag接口

django “如何”系列4:如何编写自定义模板标签和过滤器

django的模板系统自带了一系列的内建标签和过滤器,一般情况下可以满足你的要求,如果觉得需更精准的模板标签或者过滤器,你可以自己编写模板标签和过滤器,然后使用{% load %}标签使用他们. 代码布局 自定义标签和过滤器必须依赖于一个django app,也就是说,自定义标签和过滤器是绑定app的.该app应该包含一个templatetags目录,这个目录一个和model.py,views.py在同一个层级,记得在该目录下建立一个__init__.py文件一遍django知道这是一个pyth