自动化测试的三种测试报告模板

news2024/12/29 9:45:20

 🔥 交流讨论:欢迎加入我们一起学习!

🔥 资源分享耗时200+小时精选的「软件测试」资料包

🔥 教程推荐:火遍全网的《软件测试》教程  

📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

本篇文章简单记录自动化测试的三种生成HTML测试报告方式。


第一种:HTMLTestRunner

HTMLTestRunner是Python党员测试框架Unittest基础上扩展的报告模板,所以需要搭配Unittest使用。
HTMLTestRunner原版本为Python2开发的,现在已经停止维护了。可以手动更改为适配Python3版本。

文件下载(有分的大佬资助一下,hhh)或者直接复制下面的代码(新建文件HTMLTestRunner.py)就行了

# -*- coding: utf-8 -*-
"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.
The simplest way to use this is to invoke its main method. E.g.
    import unittest
    import HTMLTestRunner
    ... define your tests ...
    if __name__ == '__main__':
        HTMLTestRunner.main()
For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
    # output to a file
    fp = file('my_report.html', 'wb')
    runner = HTMLTestRunner.HTMLTestRunner(
                stream=fp,
                title='My unit test',
                description='This demonstrates the report output by HTMLTestRunner.'
                )
    # Use an external stylesheet.
    # See the Template_mixin class for more customizable options
    runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
    # run the test
    runner.run(my_test_suite)
------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

# URL: http://tungwaiyip.info/software/HTMLTestRunner.html

__author__ = "xxx"
__version__ = "0.9.1"

"""
Change History
Version 0.9.1
* 用Echarts添加执行情况统计图 (灰蓝)
Version 0.9.0
* 改成Python 3.x (灰蓝)
Version 0.8.3
* 使用 Bootstrap稍加美化 (灰蓝)
* 改为中文 (灰蓝)
Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).
Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.
Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
Version in 0.7.1
* Back port to Python 2.3 (Frank Horowitz).
* Fix missing scroll bars in detail log (Podi).
"""

# TODO: color stderr
# TODO: simplify javascript using ,ore than 1 class in the class attribute?

import datetime
import sys
import io
import time
import unittest
from xml.sax import saxutils


# ------------------------------------------------------------------------
# The redirectors below are used to capture output during testing. Output
# sent to sys.stdout and sys.stderr are automatically captured. However
# in some cases sys.stdout is already cached before HTMLTestRunner is
# invoked (e.g. calling logging.basicConfig). In order to capture those
# output, use the redirectors for the cached stream.
#
# e.g.
#   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
#   >>>

class OutputRedirector(object):
    """ Wrapper to redirect stdout or stderr """
    def __init__(self, fp):
        self.fp = fp

    def write(self, s):
        self.fp.write(s)

    def writelines(self, lines):
        self.fp.writelines(lines)

    def flush(self):
        self.fp.flush()

stdout_redirector = OutputRedirector(sys.stdout)
stderr_redirector = OutputRedirector(sys.stderr)


# ----------------------------------------------------------------------
# Template


class Template_mixin(object):
    """
    Define a HTML template for report customerization and generation.
    Overall structure of an HTML report
    HTML
    +------------------------+
    |<html>                  |
    |  <head>                |
    |                        |
    |   STYLESHEET           |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |  </head>               |
    |                        |
    |  <body>                |
    |                        |
    |   HEADING              |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |   REPORT               |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |   ENDING               |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |  </body>               |
    |</html>                 |
    +------------------------+
    """

    STATUS = {
        0: u'通过',
        1: u'失败',
        2: u'错误',
    }

    DEFAULT_TITLE = 'Unit Test Report'
    DEFAULT_DESCRIPTION = ''

    # ------------------------------------------------------------------------
    # HTML Template

    HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>%(title)s</title>
    <meta name="generator" content="%(generator)s"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    
    <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script>
    <!-- <script type="text/javascript" src="js/echarts.common.min.js"></script> -->
    
    %(stylesheet)s
    
</head>
<body>
    <script language="javascript" type="text/javascript"><!--
    output_list = Array();
    /* level - 0:Summary; 1:Failed; 2:All */
    function showCase(level) {
        trs = document.getElementsByTagName("tr");
        for (var i = 0; i < trs.length; i++) {
            tr = trs[i];
            id = tr.id;
            if (id.substr(0,2) == 'ft') {
                if (level == 2 || level == 0) {
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
            if (id.substr(0,2) == 'pt') {
                if (level < 2) {
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
        }      
    }
    function showClassDetail(cid, count) {
        var id_list = Array(count);
        var toHide = 1;
        for (var i = 0; i < count; i++) {
            tid0 = 't' + cid.substr(1) + '.' + (i+1);
            tid = 'f' + tid0;
            tr = document.getElementById(tid);
            if (!tr) {
                tid = 'p' + tid0;
                tr = document.getElementById(tid);
            }
            id_list[i] = tid;
            if (tr.className) {
                toHide = 0;
            }
        }
        for (var i = 0; i < count; i++) {
            tid = id_list[i];
            if (toHide) {
                //document.getElementById('div_'+tid).style.display = 'none'
                document.getElementById(tid).className = 'hiddenRow';
            }
            else {
                document.getElementById(tid).className = '';
            }
        }
    }
    function showTestDetail(div_id){
        var details_div = document.getElementById(div_id)
        var displayState = details_div.style.display
        // alert(displayState)
        if (displayState == 'block' || displayState == '' ) {
            
            details_div.style.display = 'none'
        }
        else {
            displayState = 'block'
            details_div.style.display = 'block'
        }
    }
    function html_escape(s) {
        s = s.replace(/&/g,'&');
        s = s.replace(/</g,'<');
        s = s.replace(/>/g,'>');
        return s;
    }
    /* obsoleted by detail in <div>
    function showOutput(id, name) {
        var w = window.open("", //url
                        name,
                        "resizable,scrollbars,status,width=800,height=450");
        d = w.document;
        d.write("<pre>");
        d.write(html_escape(output_list[id]));
        d.write("\n");
        d.write("<a href='javascript:window.close()'>close</a>\n");
        d.write("</pre>\n");
        d.close();
    }
    */
    --></script>
    <div id="div_base">
        %(heading)s
        %(report)s
        %(ending)s
        %(chart_script)s
    </div>
</body>
</html>
"""  # variables: (title, generator, stylesheet, heading, report, ending, chart_script)

    ECHARTS_SCRIPT = """
    <script type="text/javascript">
        // 基于准备好的dom,初始化echarts实例
        var myChart = echarts.init(document.getElementById('chart'));
        // 指定图表的配置项和数据
        var option = {
            title : {
                text: '测试执行情况',
                x:'center'
            },
            tooltip : {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%%)"
            },
            color: ['#95b75d', 'grey', '#b64645'],
            legend: {
                orient: 'vertical',
                left: 'left',
                data: ['通过','失败','错误']
            },
            series : [
                {
                    name: '测试执行情况',
                    type: 'pie',
                    radius : '60%%',
                    center: ['50%%', '60%%'],
                    data:[
                        {value:%(Pass)s, name:'通过'},
                        {value:%(fail)s, name:'失败'},
                        {value:%(error)s, name:'错误'}
                    ],
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        };
        // 使用刚指定的配置项和数据显示图表。
        myChart.setOption(option);
    </script>
    """  # variables: (Pass, fail, error)

    # ------------------------------------------------------------------------
    # Stylesheet
    #
    # alternatively use a <link> for external style sheet, e.g.
    #   <link rel="stylesheet" href="$url" type="text/css">

    STYLESHEET_TMPL = """
<style type="text/css" media="screen">
    body        { font-family: Microsoft YaHei,Consolas,arial,sans-serif; font-size: 80%; }
    table       { font-size: 100%; }
    pre         { white-space: pre-wrap;word-wrap: break-word; }
    /* -- heading ---------------------------------------------------------------------- */
    h1 {
        /**font-size: 16pt;
        color: gray;*/
    }
    .heading {
        margin-top: 0ex;
        margin-bottom: 1ex;
    }
    .heading .attribute {
        margin-top: 1ex;
        margin-bottom: 0;
    }
    .heading .description {
        margin-top: 2ex;
        margin-bottom: 3ex;
    }
    /* -- css div popup ------------------------------------------------------------------------ */
    a.popup_link {
    }
    a.popup_link:hover {
        color: red;
    }
    .popup_window {
        display: none;
        position: relative;
        left: 0px;
        top: 0px;
        /*border: solid #627173 1px; */
        padding: 10px;
        /*background-color: #E6E6D6; */
        font-family: "Lucida Console", "Courier New", Courier, monospace;
        text-align: left;
        font-size: 8pt;
        /* width: 500px;*/
    }
    }
    /* -- report ------------------------------------------------------------------------ */
    #show_detail_line {
        margin-top: 3ex;
        margin-bottom: 1ex;
    }
    #result_table {
        width: 99%;
    }
    #header_row {
        font-weight: bold;
        color: #303641;
        background-color: #dff0d8;
    }
    #total_row  { font-weight: bold; }
    .passClass  { background-color: #bdedbc; }
    .failClass  { background-color: #ffefa4; }
    .errorClass { background-color: #ffc9c9; }
    .passCase   { color: #5cb85c; }
    .failCase   { color: #FF6600; font-weight: bold; }
    .errorCase  { color: #c00; font-weight: bold; }
    .hiddenRow  { display: none; }
    .testcase   { margin-left: 2em; }
    /* -- ending ---------------------------------------------------------------------- */
    #ending {
    }
    #div_base {
                padding: 20px;
    }
</style>
"""

    # ------------------------------------------------------------------------
    # Heading
    #

    HEADING_TMPL = """
    <div class='page-header'>
        <h1>%(title)s</h1>
    %(parameters)s
    </div>
    <div style="float: left;width:50%%;"><p class='description'>%(description)s</p></div>
    <div id="chart" style="width:50%%;height:400px;float:left;"></div>
"""  # variables: (title, parameters, description)

    HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
"""  # variables: (name, value)

    # ------------------------------------------------------------------------
    # Report
    #

    REPORT_TMPL = u"""

    <p id='show_detail_line'>
        <a class="btn btn-primary" href='javascript:showCase(0)'>概要{ %(passrate)s }</a>
        <a class="btn btn-danger" href='javascript:showCase(1)'>未通过{ %(notPass)s }</a>
        <a class="btn btn-success" href='javascript:showCase(2)'>通过{ %(Pass)s }</a>
        <a class="btn btn-info" href='javascript:showCase(3)'>所有{ %(count)s }</a>        
    </p>
    <table id='result_table' class="table table-bordered">
        <colgroup>
            <col align='left' />
            <col align='right' />
            <col align='right' />
            <col align='right' />
            <col align='right' />
            <col align='right' />
        </colgroup>
        <tr id='header_row' class="text-center success">
            <td>用例集/测试用例</td>
            <td>总数</td>
            <td>通过</td>
            <td>失败</td>
            <td>错误</td>
            <td>查看</td>
        </tr>
        %(test_list)s
        <tr id='total_row' class="text-center active">
            <td>总计</td>
            <td>%(count)s</td>
            <td>%(Pass)s</td>
            <td>%(fail)s</td>
            <td>%(error)s</td>
            <td>通过率:%(passrate)s</td>
        </tr>
    </table>
"""  # variables: (test_list, count, Pass, fail, error, passrate)

    REPORT_CLASS_TMPL = u"""
    <tr class='%(style)s'>
        <td class="text-center">%(desc)s</td>
        <td class="text-center">%(count)s</td>
        <td class="text-center">%(Pass)s</td>
        <td class="text-center">%(fail)s</td>
        <td class="text-center">%(error)s</td>
        <td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(count)s)">详细</a></td>
    </tr>
"""  # variables: (style, desc, count, Pass, fail, error, cid)

    REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>
    <!--css div popup start-->
    <a οnfοcus='this.blur();' data-toggle="collapse" href="javascript:showTestDetail('div_%(tid)s')" >
        %(status)s</a>
    <div id='div_%(tid)s' class="collapse in">
        <pre>%(script)s</pre>
    </div>
    <!--css div popup end-->
    </td>
</tr>
"""  # variables: (tid, Class, style, desc, status)

    REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center' style="color:#428bca">%(status)s</td>
</tr>
"""  # variables: (tid, Class, style, desc, status)

    REPORT_TEST_OUTPUT_TMPL = r"""%(id)s: %(output)s"""  # variables: (id, output)

    # ------------------------------------------------------------------------
    # ENDING
    #

    ENDING_TMPL = """<div id='ending'> </div>
    <div style=" position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer">
    <a href="#"><span class="glyphicon glyphicon-eject" style = "font-size:30px;" aria-hidden="true">
    </span></a></div>
    """

# -------------------- The end of the Template class -------------------


TestResult = unittest.TestResult


class _TestResult(TestResult):
    # note: _TestResult is a pure representation of results.
    # It lacks the output and reporting ability compares to unittest._TextTestResult.

    def __init__(self, verbosity=1):
        TestResult.__init__(self)
        self.stdout0 = None
        self.stderr0 = None
        self.success_count = 0
        self.failure_count = 0
        self.error_count = 0
        self.verbosity = verbosity

        # result is a list of result in 4 tuple
        # (
        #   result code (0: success; 1: fail; 2: error),
        #   TestCase object,
        #   Test output (byte string),
        #   stack trace,
        # )
        self.result = []
        self.subtestlist = []
        self.passrate = float(0)

    def startTest(self, test):
        TestResult.startTest(self, test)
        # just one buffer for both stdout and stderr
        self.outputBuffer = io.StringIO()
        stdout_redirector.fp = self.outputBuffer
        stderr_redirector.fp = self.outputBuffer
        self.stdout0 = sys.stdout
        self.stderr0 = sys.stderr
        sys.stdout = stdout_redirector
        sys.stderr = stderr_redirector

    def complete_output(self):
        """
        Disconnect output redirection and return buffer.
        Safe to call multiple times.
        """
        if self.stdout0:
            sys.stdout = self.stdout0
            sys.stderr = self.stderr0
            self.stdout0 = None
            self.stderr0 = None
        return self.outputBuffer.getvalue()

    def stopTest(self, test):
        # Usually one of addSuccess, addError or addFailure would have been called.
        # But there are some path in unittest that would bypass this.
        # We must disconnect stdout in stopTest(), which is guaranteed to be called.
        self.complete_output()

    def addSuccess(self, test):
        if test not in self.subtestlist:
            self.success_count += 1
            TestResult.addSuccess(self, test)
            output = self.complete_output()
            self.result.append((0, test, output, ''))
            if self.verbosity > 1:
                sys.stderr.write('ok ')
                sys.stderr.write(str(test))
                sys.stderr.write('\n')
            else:
                sys.stderr.write('..')

    def addError(self, test, err):
        self.error_count += 1
        TestResult.addError(self, test, err)
        _, _exc_str = self.errors[-1]
        output = self.complete_output()
        self.result.append((2, test, output, _exc_str))
        if self.verbosity > 1:
            sys.stderr.write('E  ')
            sys.stderr.write(str(test))
            sys.stderr.write('\n')
        else:
            sys.stderr.write('E')

    def addFailure(self, test, err):
        self.failure_count += 1
        TestResult.addFailure(self, test, err)
        _, _exc_str = self.failures[-1]
        output = self.complete_output()
        self.result.append((1, test, output, _exc_str))
        if self.verbosity > 1:
            sys.stderr.write('F  ')
            sys.stderr.write(str(test))
            sys.stderr.write('\n')
        else:
            sys.stderr.write('F')

    def addSubTest(self, test, subtest, err):
        if err is not None:
            if getattr(self, 'failfast', False):
                self.stop()
            if issubclass(err[0], test.failureException):
                self.failure_count += 1
                errors = self.failures
                errors.append((subtest, self._exc_info_to_string(err, subtest)))
                output = self.complete_output()
                self.result.append((1, test, output + '\nSubTestCase Failed:\n' + str(subtest),
                                    self._exc_info_to_string(err, subtest)))
                if self.verbosity > 1:
                    sys.stderr.write('F  ')
                    sys.stderr.write(str(subtest))
                    sys.stderr.write('\n')
                else:
                    sys.stderr.write('F')
            else:
                self.error_count += 1
                errors = self.errors
                errors.append((subtest, self._exc_info_to_string(err, subtest)))
                output = self.complete_output()
                self.result.append(
                    (2, test, output + '\nSubTestCase Error:\n' + str(subtest), self._exc_info_to_string(err, subtest)))
                if self.verbosity > 1:
                    sys.stderr.write('E  ')
                    sys.stderr.write(str(subtest))
                    sys.stderr.write('\n')
                else:
                    sys.stderr.write('E')
            self._mirrorOutput = True
        else:
            self.subtestlist.append(subtest)
            self.subtestlist.append(test)
            self.success_count += 1
            output = self.complete_output()
            self.result.append((0, test, output + '\nSubTestCase Pass:\n' + str(subtest), ''))
            if self.verbosity > 1:
                sys.stderr.write('ok ')
                sys.stderr.write(str(subtest))
                sys.stderr.write('\n')
            else:
                sys.stderr.write('..')


class HTMLTestRunner(Template_mixin):

    def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
        self.stream = stream
        self.verbosity = verbosity
        if title is None:
            self.title = self.DEFAULT_TITLE
        else:
            self.title = title
        if description is None:
            self.description = self.DEFAULT_DESCRIPTION
        else:
            self.description = description


        self.startTime = datetime.datetime.now()

    def run(self, test):
        "Run the given test case or test suite."
        result = _TestResult(self.verbosity)
        test(result)
        self.stopTime = datetime.datetime.now()
        self.generateReport(test, result)
        print('\nTime Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)
        return result

    def sortResult(self, result_list):
        # unittest does not seems to run in any particular order.
        # Here at least we want to group them together by class.
        rmap = {}
        classes = []
        for n,t,o,e in result_list:
            cls = t.__class__
            if cls not in rmap:
                rmap[cls] = []
                classes.append(cls)
            rmap[cls].append((n,t,o,e))
        r = [(cls, rmap[cls]) for cls in classes]
        return r

    def getReportAttributes(self, result):
        """
        Return report attributes as a list of (name, value).
        Override this to add custom attributes.
        """
        startTime = str(self.startTime)[:19]
        duration = str(self.stopTime - self.startTime)
        status = []
        if result.success_count: status.append(u'通过 %s' % result.success_count)
        if result.failure_count: status.append(u'失败 %s' % result.failure_count)
        if result.error_count:   status.append(u'错误 %s' % result.error_count  )
        if status:
            status = ' '.join(status)
            self.passrate = str("%.2f%%" % (float(result.success_count) / float(
                result.success_count + result.failure_count + result.error_count) * 100))
        else:
            status = 'none'
        return [
            (u'开始时间', startTime),
            (u'运行时长', duration),
            (u'测试结果', status + " 通过率= "+self.passrate),
        ]

    def generateReport(self, test, result):
        report_attrs = self.getReportAttributes(result)
        generator = 'HTMLTestRunner %s' % __version__
        stylesheet = self._generate_stylesheet()
        heading = self._generate_heading(report_attrs)
        report = self._generate_report(result)
        ending = self._generate_ending()
        chart = self._generate_chart(result)
        output = self.HTML_TMPL % dict(
            title = saxutils.escape(self.title),
            generator = generator,
            stylesheet = stylesheet,
            heading = heading,
            report = report,
            ending = ending,
            chart_script = chart
        )
        self.stream.write(output.encode('utf8'))

    def _generate_stylesheet(self):
        return self.STYLESHEET_TMPL

    def _generate_heading(self, report_attrs):
        a_lines = []
        for name, value in report_attrs:
            line = self.HEADING_ATTRIBUTE_TMPL % dict(
                name = saxutils.escape(name),
                value = saxutils.escape(value),
            )
            a_lines.append(line)
        heading = self.HEADING_TMPL % dict(
            title = saxutils.escape(self.title),
            parameters = ''.join(a_lines),
            description = saxutils.escape(self.description),
        )
        return heading

    def _generate_report(self, result):
        rows = []
        sortedResult = self.sortResult(result.result)
        for cid, (cls, cls_results) in enumerate(sortedResult):
            # subtotal for a class
            np = nf = ne = 0
            for n,t,o,e in cls_results:
                if n == 0: np += 1
                elif n == 1: nf += 1
                else: ne += 1

            # format class description
            if cls.__module__ == "__main__":
                name = cls.__name__
            else:
                name = "%s.%s" % (cls.__module__, cls.__name__)
            doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
            desc = doc and '%s: %s' % (name, doc) or name

            row = self.REPORT_CLASS_TMPL % dict(
                style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
                desc = desc,
                count = np+nf+ne,
                Pass = np,
                fail = nf,
                error = ne,
                cid = 'c%s' % (cid+1),
            )
            rows.append(row)

            for tid, (n,t,o,e) in enumerate(cls_results):
                self._generate_report_test(rows, cid, tid, n, t, o, e)

        report = self.REPORT_TMPL % dict(
            test_list = ''.join(rows),
            count = str(result.success_count+result.failure_count+result.error_count),
            Pass = str(result.success_count),
            notPass = str(result.failure_count + result.error_count),
            fail = str(result.failure_count),
            error = str(result.error_count),
            passrate=self.passrate,
        )
        return report

    def _generate_chart(self, result):
        chart = self.ECHARTS_SCRIPT % dict(
            Pass=str(result.success_count),
            fail=str(result.failure_count),
            error=str(result.error_count),
        )
        return chart

    def _generate_report_test(self, rows, cid, tid, n, t, o, e):
        # e.g. 'pt1.1', 'ft1.1', etc
        has_output = bool(o or e)
        tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
        name = t.id().split('.')[-1]
        doc = t.shortDescription() or ""
        desc = doc and ('%s: %s' % (name, doc)) or name
        tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL

        script = self.REPORT_TEST_OUTPUT_TMPL % dict(
            id=tid,
            output=saxutils.escape(o+e),
        )

        row = tmpl % dict(
            tid=tid,
            Class=(n == 0 and 'hiddenRow' or 'none'),
            style=(n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase')),
            desc=desc,
            script=script,
            status=self.STATUS[n],
        )
        rows.append(row)
        if not has_output:
            return

    def _generate_ending(self):
        return self.ENDING_TMPL


##############################################################################
# Facilities for running tests from the command line
##############################################################################

# Note: Reuse unittest.TestProgram to launch test. In the future we may
# build our own launcher to support more specific command line
# parameters like test title, CSS, etc.
class TestProgram(unittest.TestProgram):
    """
    A variation of the unittest.TestProgram. Please refer to the base
    class for command line parameters.
    """
    def runTests(self):
        # Pick HTMLTestRunner as the default test runner.
        # base class's testRunner parameter is not useful because it means
        # we have to instantiate HTMLTestRunner before we know self.verbosity.
        if self.testRunner is None:
            self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
        unittest.TestProgram.runTests(self)

main = TestProgram

##############################################################################
# Executing this module from the command line
##############################################################################

if __name__ == "__main__":
    main(module=None)

例子参考:HTMLTestRunner需要结合Unittest的suite使用,将测试用例都装在到suite中后运行

import HTMLTestRunner
# 手机号码+密码登录成功用例
def test_login_success(self):
    self.result = self.lb.login_by_password_success('15622297351', 'aa111111')
    self.assertTrue(self.result, '测试用例登录失败')

if __name__ == '__main__':
    # unittest.main()
    # 文件名时间戳
    local_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
    # 保存report文件路径
    report_path = os.path.join(
        os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
        + '/report/login/' + local_time + '.html'
    )
    f = open(report_path, 'wb')
    # 实例化测试套件
    suite = unittest.TestSuite()
    # 添加测试用例
    suite.addTest(LoginTest('test_login_success'))
    suite.addTest(LoginTest('test_account_error'))
    runner = HTMLTestRunner.HTMLTestRunner(
        stream=f, title='This is Ebemate login_test_case report',
        description='这是登录测试报告', verbosity=2
    )
    runner.run(suite)
    f.close()

HTMLTestRunner运行结果图

第二种:Allure

Allure是个费钱强大的报告框架,他结合Pytest单元测试框架使用能够非常详细的展示测试用例的执行情况,以及展示各种独特的分析结果,同时还支持多种语种的切换,但目前为止它功能最强大,操作也相对较为复杂。

使用步骤

  1. Allure报告框架是有Java开发的,所以需要安装Java8以上版本
  2. 安装Pytest:pip install pytest
  3. 安装allure:pip install allure-pytest
  4. 执行pytest命令运行测试用例,生成测试结果数据:pytest -s -q --alluredir 'D:\\PO\\TestResult\\allureReport'(路径为生成测试数据结果存放位置路径)
  5. 安装Allure Command Line(下载):工具解压后(放置位置看自己,可以放项目里也可以放其他地方),配置系统环境变量PATH(\安装路径\allure-commandline\bin)方便其他项目调用。配置好后,命令行输入allure出现Usage使用说明就成功。
  6. 将第4生成的测试结果数据生成测试报告:allure generate directory-with-results/ -o directory-with-report
    directory-with-results:为测试结果数据文件存放路径(D:\PO\TestResult\allureReport)
    directory-with-report:用于自定义HTML报告生成到哪个路径下(D:\PO\TestResult\Report)
  7. 运行生成文件的index.html
    在这里插入图片描述
    Allure运行结果图
第三种:BeautifulReport

BeautifulReport是基于Unittest党员测试框架的一个可以生成HTML格式的自动化测试报告的报告框架,它为用户提供了既简洁又美观的HTML模板,将生成的结果添加到模板中,然后在指定的目录下生成HTML格式报告。
使用步骤

  1. 获取报告模板地址,直接下载。
  2. 项目中放入template文件和BeautifulReport.py
  3. 新建Run_Automation_BR.py,编写执行测试用例代码,并调用BeautifulReport
import unittest
import os
from Util.BeautifulReport import BeautifulReport
if __name__ == '__main__':
    # 获取当前文件所在目录的父目录的绝对路径
    parent_directory_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    # 定义测试集合
    test_suite = unittest.defaultTestLoader.discover('TestScript', pattern='test*.py')
    result = BeautifulReport(test_suite)
    result.report(filename='测试报告', description='测试报告', log_path=parent_directory_path + "\\PO\\TestResult\\Report\\")

运行Error
如果出现上述运行Error,根据提示,可以看到是项目没有找到默认路径下的template文件。存在两种解决方法:
第一种:根据异常信息,将template文件放到系统提示的路径中去。
第二种:修改BeautifulReport.py源码中template文件的实际路径。代码如下:

# BeautifulReport.py第67行
# 获取当前文件所在目录的父目录的绝对路径
parent_directory_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class PATH:
    """ all file PATH meta """
    # config_tmp_path = SITE_PAKAGE_PATH + '/BeautifulReport/template/template'
    # 重新定义template路径
    config_tmp_path = parent_directory_path + '/template/template'
  1. 再次运行Run_Automation_BR.py文件,生成报告
  2. 在这里插入图片描述
    BeautifulReport运行结果

最后我邀请你进入我们的【软件测试学习交流群:785128166】, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路

作为一个软件测试的过来人,我想尽自己最大的努力,帮助每一个伙伴都能顺利找到工作。所以我整理了下面这份资源,现在免费分享给大家,有需要的小伙伴可以关注【公众号:程序员二黑】自提!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1615803.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《HCIP-openEuler实验指导手册》1.3Apache动态功能模块加载卸载练习

1.3.1 配置思路 mod_status 模块可以帮助管理员通过web界面监控Apache运行状态&#xff0c;通过LoadModule指令加载该模块&#xff0c;再配置相关权限&#xff0c;并开启ExtendedStatus后&#xff0c;即可使用该模块。 1.3.2 配置步骤 检查mod_status模块状态&#xff08;使…

[MySQL]运算符

1. 算术运算符 (1). 算术运算符 : , -, *, / 或 DIV, % 或MOD. (2). 例 : (3). 注 : DUAL是伪表.可以看到4/2结果为小数&#xff0c;并不会截断小数部分.(可能与其他语言不同&#xff0c;比如java中&#xff0c;两个操作数如果是整数&#xff0c;则计算得到的也是整数&…

ASP.NET大文件分片上传

ASP.NET大文件分片上传&#xff0c;C#上传大型视频文件到服务器,解决方案&#xff0c;用C# 实现断点续传 (HTTP)&#xff0c;ASP.NET实现文件夹的上传和下载&#xff0c;.NET使用WEBUPLOADER做大文件的分块和断点续传&#xff0c;ASP.NET实现文件上传和下载&#xff0c;完美解决…

JavaWeb--前端工程化

目录 1. 前端工程化 1.1. 概述 1.2. 前端工程化实现技术栈 2. ECMA6Script 2.1. es6的介绍 2.2. es6 变量 / 模版字符串 2.2.1. let 与 var 的差别 2.2.2. const 与 var 的差异 2.2.3. 模板字符串 2.3. 解构表达式 / 赋值 2.3.1. 数组解构赋值 2.3.2. 对象解构赋值 …

CFCASSL证书的网络安全解决方案

在数字化时代&#xff0c;网络信息安全的重要性不言而喻。随着电子商务、在线交易、远程办公等互联网活动的日益普及&#xff0c;确保数据传输的安全性与隐私保护成为企业和用户共同关注的焦点。在此背景下&#xff0c;CFCA SSL证书作为一种权威、高效的网络安全解决方案&#…

2024 应届校招经验分享(非互联网篇)

&#x1f4e2;&#x1f4e2;&#x1f4e2; 2024年应届毕业生&#xff0c;浙江大学计算机研究生。自23年秋招开始至今&#xff0c;博主先后拿到了互联网行业、机械制造行业、国企央企、高校体制等多家企业和单位的数十个Offer&#xff0c;了解多行业中的计算机求职方向&#xff…

使用JavaScript创建数组,并对其进行冒泡排序

JavaScript创建数组方式 字面量方式&#xff1a;使用方括号 [] 来创建数组&#xff0c;并在方括号内按顺序列出数组元素。 let arr [1, 2, 3, 4, 5]; Array() 构造函数方式&#xff1a;使用 new Array() 构造函数来创建数组&#xff0c;并传入数组元素作为参数。 let arr ne…

C++设计模式:中介者模式(十五)

1、定义与动机 定义&#xff1a;用一个中介对象来封装&#xff08;封装变化&#xff09;一系列的对象交互。中介者使各个对象不需要显示的相互引用&#xff08;编译时依赖 -> 运行时依赖&#xff09;&#xff0c;从而使其耦合松散&#xff08;管理变化&#xff09;&#xff…

Docker基础+虚拟化概念

目录 一、虚拟化简介 1、虚拟化概述 2、cpu的时间分片&#xff08;cpu虚拟化&#xff09; 3、cpu虚拟化性性能瓶颈 4、虚拟化工作 4.1虚拟机工作原理 4.2两大核心组件:QEMU、KVM 4.2.1QEMU&#xff1a; 4.2.2KVM&#xff1a; 5、虚拟化类型 ①全虚拟化&#xff1a; …

CSS布局 Flex 和 Grid

在 CSS 中&#xff0c;理解 flex 和 Grid 布局非常重要&#xff0c;今天把这两个重要知识点回顾一下。 Flexbox 弹性盒子布局 弹性布局支持 flex、inline-flex&#xff0c;支持块和内联。 容器 轴的概念&#xff0c;在 Flexbox&#xff0c;有主轴和侧轴的概念&#xff0c;轴…

基于python实现web漏洞挖掘技术的研究(django)

基于python实现web漏洞挖掘技术的研究(django) 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;网络爬虫&#xff0c;SQL注入&#xff0c;XSS漏洞工具&#xff1a;pycharm、Navicat、Maven 系统的实现与漏洞挖掘 系统的首页面 此次的系统首页面是登录的页…

PLC_博图系列☞P_TRIG:扫描 RLO 的信号上升沿

PLC_博图系列☞P_TRIG&#xff1a;扫描 RLO 的信号上升沿 文章目录 PLC_博图系列☞P_TRIG&#xff1a;扫描 RLO 的信号上升沿背景介绍P_TRIG&#xff1a; 扫描 RLO 的信号上升沿说明参数示例 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 P_TRIG 背景介绍 这是一…

卷积神经网络(CNN)对验证码图片识别案例

数据集 数据集下载 链接&#xff1a;https://pan.baidu.com/s/1ypNNQkR1_ZK-_KO92x6Phw?pwd6753 提取码&#xff1a;6753 图片1 -->NZPP 一个样本对应四个目标值 NZPP ---【13,25,15,15】 使用one-hot编码转换 第一个位置&#xff1a;[0,0,0,0,0,0,0,0,0,0,0,0,…

最强开源大模型Meta LIama3抢先在线体验!

4月19日Facebook母公司Meta重磅推出了其迄今最强大的开源人工智能&#xff08;AI&#xff09;模型——Llama 3。模型分为两种规模&#xff1a;8B 和 70B 参数&#xff0c;每种规模都提供预训练基础版和指令调优版。最强开源大语言模型Meta LIama3可以在线体验啦&#xff01; G…

心理学|变态心理学健康心理学——躯体疾病患者的一般心理特点

一、对客观世界和自身价值的态度发生改变 患者除了内部器官有器质或功能障碍外&#xff0c;他们的自我感觉和整个精神状态也会发生变化。使人改变对周围事物的感受和态度&#xff0c;也可以改变患者对自身存在价值的态度。这种主观态度的改变&#xff0c;可以使患者把自己置于人…

wps免登录绕路

打开注册表 regedit 新建字符串值--> false

第25天:安全开发-PHP应用文件管理包含写入删除下载上传遍历安全

第二十五天 一、PHP文件管理-下载&删除功能实现 1.文件上传&#xff1a; 无过滤机制黑名单过滤机制白名单过滤机制文件类型过滤机制 2.文件删除&#xff1a; unlink() 文件删除函数调用命令删除&#xff1a;system shell_exec exec等 3. 文件下载&#xff1a; 修改HT…

我独自升级崛起怎么下载 一文分享我独自升级崛起游戏下载教程

我独自升级崛起怎么下载 一文分享我独自升级崛起游戏下载教程 我独自升级&#xff1a;崛起是一款由韩国漫画改编而成的热门多人网络在线联机游戏&#xff0c;这款游戏是一款的角色扮演类型游戏&#xff0c;游戏有着独一无二的剧情模式。小伙伴们在游戏中可以体验到独特的成长系…

URL解析

目录 URIURLURL语法相对URLURL中的转义 现在与未来PURL 在 URL出现之前&#xff0c;人们如果想访问网络中的资源&#xff0c;就需要使用不同的 应用程序&#xff0c;如共享文件需要使用 FTP程序&#xff0c;想要发送邮件必须使用 邮件程序&#xff0c;想要看新闻那只能使用…

VSCode 配置 C/C++ 环境

1 安装 VSCode 直接去官网(https://code.visualstudio.com/)下载并安装即可。 2 配置C/C编译环境 方案一 如果是在Windows&#xff0c;需要安装 MingW&#xff0c;可以去官网(https://sourceforge.net/projects/mingw-w64/)下载安装包。 注意安装路径不要出现中文。 打开 w…