专栏首页7DGroup自动化工具之Appium之报告自定义

自动化工具之Appium之报告自定义

背景

自动化测试用例跑完后报告展示是体现咱们价值的一个地方咱们先看原始报告:

上面报告虽然麻雀虽小但五脏俱全,但是如果用这个发送报告不是很美观,如果错误没有截图与日志,通过观察testng有需要可以继承的监听,可以自定义报告;

自定义报告展示

点击log弹出对话框并且记录操作日志

保存结果实体

import java.util.List;
/** * @author liwen * @Title: TestResult * @Description: 用于存储测试结果 * @date 2019/11/23 / 17:28 */public class TestResult {    /**     * 测试方法名     */    private String testName;    /**     * 测试类名     */    private String className;    /**     * 用例名称     */    private String caseName;    /**     * 测试用参数     */    private String params;    /**     * 测试描述     */    private String description;    /**     * 报告输出日志Reporter Output     */    private List<String> output;    /**     * 测试异常原因     */    private Throwable throwable;
    /**     * 线程信息     */    private String throwableTrace;    /**     * 状态     */    private int status;    /**     * 持续时间     */    private String duration;    /**     * 是否成功     */    private boolean success;//省略get/set

工具类

import org.testng.ITestResult;import java.util.LinkedList;import java.util.List;
/** * @author liwen * @Title: TestResultCollection * @Description: testng采用数据驱动,一个测试类可以有多个测试用例集合,每个测试类,应该有个测试结果集 * @date 2019/11/21 / 19:01 */public class TestResultCollection {    private int totalSize = 0;
    private int successSize = 0;
    private int failedSize = 0;
    private int errorSize = 0;
    private int skippedSize = 0;    private List<TestResult> resultList;
    public void addTestResult(TestResult result) {        if (resultList == null) {            resultList = new LinkedList<>();        }        resultList.add(result);
        switch (result.getStatus()) {            case ITestResult.FAILURE:                failedSize += 1;                break;            case ITestResult.SUCCESS:                successSize += 1;                break;            case ITestResult.SKIP:                skippedSize += 1;                break;        }
        totalSize += 1;    }//省略get/set

ReporterListener代码

/** * @author liwen * @Title: ReporterListener * @Description: 自定义报告监听类 * @date 2019/11/21 / 18:56 */public class ReporterListener implements IReporter, ITestListener {    private static final Logger log = LoggerFactory.getLogger(DriverBase.class);    private static final NumberFormat DURATION_FORMAT = new DecimalFormat("#0.000");
    @Override    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {        List<ITestResult> list = new LinkedList<>();        Date startDate = new Date();        Date endDate = new Date();
        int TOTAL = 0;        int SUCCESS = 1;        int FAILED = 0;        int ERROR = 0;        int SKIPPED = 0;        for (ISuite suite : suites) {            Map<String, ISuiteResult> suiteResults = suite.getResults();            for (ISuiteResult suiteResult : suiteResults.values()) {                ITestContext testContext = suiteResult.getTestContext();
                startDate = startDate.getTime() > testContext.getStartDate().getTime() ? testContext.getStartDate() : startDate;
                if (endDate == null) {                    endDate = testContext.getEndDate();                } else {                    endDate = endDate.getTime() < testContext.getEndDate().getTime() ? testContext.getEndDate() : endDate;                }
                IResultMap passedTests = testContext.getPassedTests();                IResultMap failedTests = testContext.getFailedTests();                IResultMap skippedTests = testContext.getSkippedTests();                IResultMap failedConfig = testContext.getFailedConfigurations();
                SUCCESS += passedTests.size();                FAILED += failedTests.size();                SKIPPED += skippedTests.size();                ERROR += failedConfig.size();
                list.addAll(this.listTestResult(passedTests));                list.addAll(this.listTestResult(failedTests));                list.addAll(this.listTestResult(skippedTests));                list.addAll(this.listTestResult(failedConfig));            }        }        /* 计算总数 */        TOTAL = SUCCESS + FAILED + SKIPPED + ERROR;
        this.sort(list);        Map<String, TestResultCollection> collections = this.parse(list);        VelocityContext context = new VelocityContext();
        context.put("TOTAL", TOTAL);        context.put("mobileModel", OperationalCmd.getMobileModel());        context.put("versionName", OperationalCmd.getVersionNameInfo());        context.put("SUCCESS", SUCCESS);        context.put("FAILED", FAILED);        context.put("ERROR", ERROR);        context.put("SKIPPED", SKIPPED);        context.put("startTime", ReporterListener.formatDate(startDate.getTime()) + "<--->" + ReporterListener.formatDate(endDate.getTime()));        context.put("DURATION", ReporterListener.formatDuration(endDate.getTime() - startDate.getTime()));        context.put("results", collections);        write(context, outputDirectory);    }
    /**     * 输出模板     *     * @param context     * @param outputDirectory     */    private void write(VelocityContext context, String outputDirectory) {        if (!new File(outputDirectory).exists()) {            new File(outputDirectory).mkdirs();        }        //获取报告模板        File f = new File("");        String absolutePath = f.getAbsolutePath();        String fileDir = absolutePath + "/template/";        String reslutpath = outputDirectory + "/html/report" + ReporterListener.formateDate() + ".html";        File outfile = new File(reslutpath);        if (!outfile.exists()) {            outfile.mkdirs();        }        try {            //写文件            VelocityEngine ve = new VelocityEngine();            Properties p = new Properties();            p.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, fileDir);            p.setProperty(Velocity.ENCODING_DEFAULT, "utf-8");            p.setProperty(Velocity.INPUT_ENCODING, "utf-8");            ve.init(p);
            Template t = ve.getTemplate("reportnew.vm");            //输出结果            OutputStream out = new FileOutputStream(new File(reslutpath));            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));            // 转换输出            t.merge(context, writer);            writer.flush();            log.info("报告位置:" + reslutpath);        } catch (IOException e) {            e.printStackTrace();        }    }
    /**     * 排序规则     *     * @param list     */    private void sort(List<ITestResult> list) {        Collections.sort(list, new Comparator<ITestResult>() {            @Override            public int compare(ITestResult r1, ITestResult r2) {                if (r1.getStatus() < r2.getStatus()) {                    return 1;                } else {                    return -1;                }            }        });    }
    private LinkedList<ITestResult> listTestResult(IResultMap resultMap) {        Set<ITestResult> results = resultMap.getAllResults();        return new LinkedList<>(results);    }
    private Map<String, TestResultCollection> parse(List<ITestResult> list) {
        Map<String, TestResultCollection> collectionMap = new HashMap<>();
        for (ITestResult t : list) {            String className = t.getTestClass().getName();            if (collectionMap.containsKey(className)) {                TestResultCollection collection = collectionMap.get(className);                collection.addTestResult(toTestResult(t));
            } else {                TestResultCollection collection = new TestResultCollection();                collection.addTestResult(toTestResult(t));                collectionMap.put(className, collection);            }        }
        return collectionMap;    }
    /**     * 输出报表解析     * @param t     * @return     */    private appout.reporter.TestResult toTestResult(ITestResult t) {        TestResult testResult = new TestResult();        Object[] params = t.getParameters();
        if (params != null && params.length >= 1) {            String caseId = (String) params[0];            testResult.setCaseName(caseId);        } else {            testResult.setCaseName("null");        }        testResult.setClassName(t.getTestClass().getName());        testResult.setParams(getParams(t));        testResult.setTestName(t.getName());        testResult.setDescription(t.getMethod().getDescription());        testResult.setStatus(t.getStatus());        //异常        testResult.setThrowableTrace("class: " + t.getTestClass().getName() + " <br/> method: " + t.getName() + " <br/> error: " + t.getThrowable());        testResult.setThrowable(t.getThrowable());        long duration = t.getEndMillis() - t.getStartMillis();        testResult.setDuration(formatDuration(duration));        //日志        testResult.setOutput(Reporter.getOutput(t));        return testResult;    }
    /**     * 每次调用测试@Test之前调用     *     * @param result     */    @Override    public void onTestStart(ITestResult result) {        logTestStart(result);
    }
    /**     * 用例执行结束后,用例执行成功时调用     *     * @param result     */    @Override    public void onTestSuccess(ITestResult result) {        logTestEnd(result, "Success");    }
    /**     * 用例执行结束后,用例执行失败时调用     * 跑fail则截图 获取屏幕截图     *     * @param result     */
    @Override    public void onTestFailure(ITestResult result) {
        AppiumDriver driver = DriverBase.getDriver();        File srcFile = driver.getScreenshotAs(OutputType.FILE);
        File location = new File("./test-output/html/result/screenshots");        if (!location.exists()) {            location.mkdirs();        }        String dest = result.getMethod().getRealClass().getSimpleName() + "." + result.getMethod().getMethodName();        String s = dest + "_" + formateDate() + ".png";        File targetFile =                new File(location + "/" + s);        log.info("截图位置:");        Reporter.log("<font color=\"#FF0000\">截图位置</font><br /> " + targetFile.getPath());        log.info("------file is ---- " + targetFile.getPath());        try {            FileUtils.copyFile(srcFile, targetFile);        } catch (IOException e) {            e.printStackTrace();        }        logTestEnd(result, "Failed");        //报告截图后面显示        Reporter.log("<img  src=\"./result/screenshots/" + s + "\" width=\"64\" height=\"64\" alt=\"***\"  onMouseover=\"this.width=353; this.height=613\" onMouseout=\"this.width=64;this.height=64\" />");    }
    /**     * 用例执行结束后,用例执行skip时调用     *     * @param result     */    @Override    public void onTestSkipped(ITestResult result) {        logTestEnd(result, "Skipped");    }
    /**     * 每次方法失败但是已经使用successPercentage进行注释时调用,并且此失败仍保留在请求的成功百分比之内。     *     * @param result     */    @Override    public void onTestFailedButWithinSuccessPercentage(ITestResult result) {        LogUtil.fatal(result.getTestName());        logTestEnd(result, "FailedButWithinSuccessPercentage");    }
    /**     * 在测试类被实例化之后调用,并在调用任何配置方法之前调用。     *     * @param context     */    @Override    public void onStart(ITestContext context) {        LogUtil.startTestCase(context.getName());        return;    }
    /**     * 在所有测试运行之后调用,并且所有的配置方法都被调用     *     * @param context     */    @Override    public void onFinish(ITestContext context) {        LogUtil.endTestCase(context.getName());        return;    }
    /**     * 在用例执行结束时,打印用例的执行结果信息     */    protected void logTestEnd(ITestResult tr, String result) {        Reporter.log(String.format("=============Result: %s=============", result), true);
    }
    /**     * 在用例开始时,打印用例的一些信息,比如@Test对应的方法名,用例的描述等等     */    protected void logTestStart(ITestResult tr) {        Reporter.log(String.format("=============Run: %s===============", tr.getMethod().getMethodName()), true);        Reporter.log(String.format("用例描述: %s, 优先级: %s", tr.getMethod().getDescription(), tr.getMethod().getPriority()),                true);        return;    }
    /**     * 日期格式化     *     * @return date     */    public static String formateDate() {        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");        Calendar cal = Calendar.getInstance();        Date date = cal.getTime();        return sf.format(date);    }
    /**     * 时间转换     *     * @param date     * @return     */    public static String formatDate(long date) {        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        return formatter.format(date);    }
    public static String formatDuration(long elapsed) {        double seconds = (double) elapsed / 1000;        return DURATION_FORMAT.format(seconds);    }
    /**     * 获取方法参数,以逗号分隔     *     * @param result     * @return     */    public static 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);    }
    /**     * 将object 转换为String     * @param argument     * @return     */    private static 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();        }    }

    /**     * 将集合转换为以逗号分隔的字符串     * @param strings     * @return     */    private static 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();    }
}

模板:

css

body{background-color:#f2f2f2;color:#333;margin:0 auto;width:960px}#summary{width:960px;margin-bottom:20px}#summary th{background-color:skyblue;padding:5px 12px}#summary td{background-color:lightblue;text-align:center;padding:4px 8px}.details{width:960px;margin-bottom:20px}.details th{background-color:skyblue;padding:5px 12px}.details tr .passed{background-color:lightgreen}.details tr .failed{background-color:red}.details tr .unchecked{background-color:gray}.details td{background-color:lightblue;padding:5px 12px}.details .detail{background-color:lightgrey;font-size:smaller;padding:5px 10px;text-align:center}.details .success{background-color:greenyellow}.details .error{background-color:red}.details .failure{background-color:salmon}.details .skipped{background-color:gray}.button{font-size:1em;padding:6px;width:4em;text-align:center;background-color:#06d85f;border-radius:20px/50px;cursor:pointer;transition:all .3s ease-out}a.button{color:gray;text-decoration:none}.button:hover{background:#2cffbd}.overlay{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.7);transition:opacity 500ms;visibility:hidden;opacity:0}.overlay:target{visibility:visible;opacity:1}.popup{margin:70px auto;padding:20px;background:#fff;border-radius:10px;width:50%;position:relative;transition:all 3s ease-in-out}.popup h2{margin-top:0;color:#333;font-family:Tahoma,Arial,sans-serif}.popup .close{position:absolute;top:20px;right:30px;transition:all 200ms;font-size:30px;font-weight:bold;text-decoration:none;color:#333}.popup .close:hover{color:#06d85f}.popup .content{max-height:80%;overflow:auto;text-align:left}@media screen and (max-width:700px){.box{width:70%}.popup{width:70%}}

report.vm

<head>    <meta content="text/html; charset=utf-8" http-equiv="content-type"/>    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>UI自动</title>    <style>    css在上面      </style></head>
<body><br><h1 align="center">UI自动化回归报告</h1>
<h2>汇总信息</h2><table id="summary">
    <tr>        <th>开始与结束时间</th>        <td colspan="2">${startTime}</td>        <th>执行时间</th>        <td colspan="2">$DURATION seconds</td>    </tr>    <tr>        <th>运行版本与系统版本</th>        <td colspan="2">${versionName}</td>        <th>设备型号</th>        <td colspan="2">${mobileModel}</td>    </tr>    <tr>        <th>TOTAL</th>        <th>SUCCESS</th>        <th>FAILED</th>        <th>ERROR</th>        <th>SKIPPED</th>    </tr>    <tr>        <td>$TOTAL</td>        <td>$SUCCESS</td>        <td>$FAILED</td>        <td>$ERROR</td>        <td>$SKIPPED</td>    </tr></table>
<h2>详情</h2>
    #foreach($result in $results.entrySet())        #set($item = $result.value)    <table id="$result.key" class="details">        <tr>            <th>测试类</th>            <td colspan="4">$result.key</td>        </tr>        <tr>            <td>TOTAL: $item.totalSize</td>            <td>SUCCESS: $item.successSize</td>            <td>FAILED: $item.failedSize</td>            <td>ERROR: $item.errorSize</td>            <td>SKIPPED: $item.skippedSize</td>        </tr>        <tr>            <th>Status</th>            <th>Method</th>            <th>Description</th>            <th>Duration</th>            <th>Detail</th>        </tr>        #foreach($testResult in $item.resultList)            <tr>                #if($testResult.status==1)                    <th class="success" style="width:5em;">success                    </td>                #elseif($testResult.status==2)                    <th class="failure" style="width:5em;">failure                    </td>                #elseif($testResult.status==3)                    <th class="skipped" style="width:5em;">skipped                    </td>                #end                <td>$testResult.testName</td>                <td>${testResult.description}</td>                <td>${testResult.duration} seconds</td>                <td class="detail">                    <a class="button" href="#popup_log_${testResult.caseName}_${testResult.testName}">log</a>                    <div id="popup_log_${testResult.caseName}_${testResult.testName}" class="overlay">                        <div class="popup">                            <h2>Request and Response data</h2>                            <a class="close" href="">&times;</a>                            <div class="content">                                <h3>Response:</h3>                                <div style="overflow: auto">                                    <table>                                        <tr>                                            <th>日志</th>                                            <td>                                                #foreach($msg in $testResult.output)                                                    <pre>$msg</pre>                                                #end                                            </td>                                        </tr>                                        #if($testResult.status==2)                                            <tr>                                                <th>异常</th>                                                <td>                                                    <pre>$testResult.throwableTrace</pre>                                                </td>                                            </tr>                                        #end                                    </table>                                </div>                            </div>                        </div>                    </div>                </td>            </tr>        #end    </table>    #end<a href="#top">Android前端UI自动化</a></body>

注意:report.vm存放路径,否则路径不对会找不到

执行xml:

<?xml version="1.0" encoding="UTF-8"?>
<suite name="UI自动化" parallel="tests" thread-count="1">    <listeners>        <listener class-name="myseven.reporter.ReporterListener"/>    </listeners>    <test name="M6TGLMA721108530">        <parameter name="udid" value="M6TGLMA721108530"/>        <parameter name="port" value="5190"/>        <classes>            <class name="autotest.runbase.usecase.SearchReTest"/>        </classes>    </test></suite>

总结:

只要通过上面代码就能自定义自己的报告,希望给大家一点帮助,其实这个模板只有改下就能成为接口测试报告。

源码位置:https://github.com/357712148/bodygit.git

送大家一句话

假如真有来世,我愿生生世世为人,只做芸芸众生中的一个,哪怕一生贫困清苦,浪迹天涯,只要能爱恨歌哭,只要能心遂所愿。

——仓央嘉措

本文分享自微信公众号 - 7DGroup(Zee_7DGroup),作者:李文

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 走进Java接口测试之TestNg自定报告简单学习

    高楼Zee
  • 性能工具之Loadrunner简单脚本开发方式

    LoadRunner工具在做性能测试也算是当时的老大,随着互联网发展,很多开源性能工具逐渐踊跃出来,但在传统行业中该工具还是有市场。今天咱们聊一聊该...

    高楼Zee
  • 走进Java接口测试之使用JavaMailSender发送邮件

    一般我们在做接口自动化时,都会通过钉钉或者邮件的方式通知测试结果信息。而且基本上邮件的内容都是测试报告。所以,今天就来讲讲如何利用 pring提供的 JavaM...

    高楼Zee
  • 用户登陆后的显示权限列表

    微醺
  • springboot(15)Spring Security

    IT故事会
  • Springboot用官方建议访问Html页面并接传值

    我们以前通常习惯用webapp来放置jsp页面,但是到了Springboot中,官方建议用Static文件夹来存放及静态的资源,

    Dream城堡
  • Spring Boot2(五):使用Spring Boot结合Thymeleaf模板引擎使用总结

    一般来说,常用的模板引擎有JSP、Velocity、Freemarker、Thymeleaf 。

    鸟不拉屎
  • SpringBoot(四)之thymeleaf的使用

    这篇文章将更加全面详细的介绍thymeleaf的使用。thymeleaf 是新一代的模板引擎,在spring4.0中推荐使用thymeleaf来做前端模版引擎。...

    用户1195962
  • Markdown 快速生成表格

    这个可以说是大杀器了,我们只需要下载一个东西就行了,这个是知乎用户幻灰龙写的东西,亲测有效

    IT小马哥
  • 包含合并单元格的固定表头 原

    $('#outtableDiv').scroll(function() {             var scrollTop=$('#outtableDiv...

    tianyawhl

扫码关注云+社区

领取腾讯云代金券