• 如何自己实现一个HTMLRunner


    在使用unittest框架时,我们常常需要下载一个HTMLRunnerCN.py用来生成HTML格式的报告,那么我们能不能自己实现一个呢?

    HTMLRunner是模仿unittest自带的TextTestRunner()实现的,我们先来看看TextTestRunner()的运行流程。

    TextTestRunner使用方法

    import unittest
    
    suite = unittest.defaultTestLoader.discover("./")
    
    with open("report.txt", "w") as f:   # 将运行结果保存为txt文件
        unittest.TextTestRunner().run(suite)
    

    运行流程

    1. TextTestRunner 内部实现了一个TextTestResult(继承自unittest.TestResult类)来记录测试结果
    2. TextTestRunner().run()实际调用suite(result) suite.run(result) (result用来记录结果)
    3. suite.run(result)会遍历suite中的用例,依次调用case(result)case.run(result)
    4. case.run(result)时,首先会调用result.testRun+=1然后执行用例方法testMethod(), 如果用例失败、出错、跳过则用例会分别调用result.addSuccess(),result.addFailure()等方法,在对应的result.failures,result.errors列表中添加用例信息,默认成功用例result中不处理
    5. 运行完返回result(测试结果对象)

    unittest.TextTestRunner和网上的HTMLRunner都是基于stream流去写的文件,每执行一条用例,把对应的结果和信息写到流中,最后输出成文件,这种方法需要很多的细节控制,比较复杂。

    我们可以采用解析执行完返回result结果,通过Jinjia2模板引擎渲染,将数据渲染到模板里,形成报告文件。

    Jinjia2是一个三方包,可以将模板代码中的{{变量名}}等占位符将变量值渲染进去,支持循环和if判断。安装方法pip install jinjia2

    实现步骤

    1. 首先我们要写个模板
    TPL = '''
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>{{title}}</title>
        </head>
        <body>
            <h2>{{title}}</h2> 
            <h3>{{description}}</h3>
            <br/>
            <table border="1">
                {% for case in cases %}
                <tr>
                    <td>{{case.name}}</td>
                    <td>{{case.status}}</td>
                    <td>{{case.exec_info}}</td>
                </tr>
                {% endfor %}
            </table>
        </body>
    </html>
    '''
    
    • {{title}},{{description}}能将传入的数据中的相应的变量值填充进去
    • {% for case in cases%} ...{% endfor %}遍历cases列表中每一个用例数据,每个生成一个表格行(<tr>...</tr>
    1. 自定义一个Result类
      由于默认的TestResult()将各种状态的用例分散存的,我们可以自定义一个Result类来处理用例成功、失败、出错执行的操作
    class Result(unittest.TestResult):
        def __init__(self):
            super().__init__()
            self.cases = []
    
        def addSuccess(self, test):
            self.cases.append({"name": test.id(), "status": "pass", "exec_info": ""})
    
        def addError(self, test, exec_info):
            self.cases.append({"name": test.id(), "status": "error",
                                 "exec_info": self._exc_info_to_string(exec_info, test)
                              .replace("
    ", "<br/>")})
    
        def addFailure(self, test, exec_info):
            self.cases.append({"name": test.id(), "status": "fail",
                                 "exec_info": self._exc_info_to_string(exec_info, test)
                              .replace("
    ", "<br/>")})
    
        def addSkip(self, test, reason):
            self.cases.append({"name": test.id(), "status": "skip", "exec_info": reason))
    
    • addSuccess等方法对应用例成功或其他状态时在result结果中的操作
    • _exec_info_to_string: 默认用例传过来的exec_info是Trackback对象
      ,需要转换为字符串,replace 转为网页的换行<br/>
    1. 实现我们的HTMLRunner
    class HTMLRunner(object):
        def __init__(self, output, title="Test Report", description=""):
            self.file = output
            self.title = title
            self.description = description
    
        def run(self, suite):
            result = Result()   # 用于保存测试结果
            suite(result)  # 执行测试
    
            # 渲染数据到模板
            content = Template(TPL).render({"title": self.title,
                                            "description": self.description,
                                            "cases": result.cases})
            with open(self.file, "w") as f:
                f.write(content)  # 写入文件
            return result
    
    1. 使用方法(自己准备几条用例)
    suite = unittest.defaultTestLoader.discover("./")
    
    HTMLRunner(output="report.html", 
               title="测试报告", 
               description="测试报告描述").run(suite)
    

    生成的测试报告
    测试报告

    整体代码

    美化格式,增加执行统计信息

    import time
    import unittest
    from jinja2 import Template
    
    
    TPL = '''
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{title}}</title>
        <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css">
    </head>
    <body>
    <div class="container">
        <h1 class="pt-4">测试报告</h1>
        <h6>测试报告描述信息</h6>
        <h6>执行: {{run_num}} 通过: {{pass_num}} 失败: {{fail_num}} 出错: {{error_num}} 跳过: {{skipped_num}}</h6>
        <h6 class="pb-2">执行时间: {{duration}}s</h6>
        <table class="table table-striped">
            <thead><tr><th>用例名</th><th>状态</th><th>执行信息</th></tr></thead>
            <tbody>
                {% for case in cases %}
                <tr><td>{{case.name}}</td><td>{{case.status}}</td><td>{{case.exec_info}}</td></tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
    
    </body>
    </html>
    '''
    
    
    class Result(unittest.TestResult):
        def __init__(self):
            super().__init__()
            self.success = []
            self.cases = []
    
        def addSuccess(self, test):
            self.success.append(test)
            self.cases.append({"name": test.id(), "status": "pass", "exec_info": ""})
    
        def addError(self, test, exec_info):
            self.errors.append((test, exec_info))
            self.cases.append({"name": test.id(), "status": "error",
                               "exec_info": self._exc_info_to_string(exec_info, test)
                              .replace("
    ", "<br/>")})
    
        def addFailure(self, test, exec_info):
            self.failures.append((test, exec_info))
            self.cases.append({"name": test.id(), "status": "fail",
                               "exec_info": self._exc_info_to_string(exec_info, test)
                              .replace("
    ", "<br/>")})
    
        def addSkip(self, test, exec_info):
            self.skipped.append((test, exec_info))
            self.cases.append({"name": test.id(), "status": "skip",
                               "exec_info": self._exc_info_to_string(exec_info, test)
                              .replace("
    ", "<br/>")})
    
        def addExpectedFailure(self, test, exec_info):
            self.success.append(test)
            self.cases.append({"name": test.id(), "status": "pass",
                               "exec_info": self._exc_info_to_string(exec_info, test)
                              .replace("
    ", "<br/>")})
    
        def addUnexpectedSuccess(self, test):
            self.failures.append((test, "UnexpectedSuccess"))
            self.cases.append({"name": test.id(), "status": "fail", "exec_info": "UnexpectedSuccess"})
    
    
    class HTMLRunner(object):
        def __init__(self, output, title="Test Report", description=""):
            self.file = output
            self.title = title
            self.description = description
    
        def run(self, suite):
            result = Result()   # 用于保存测试结果
            start_time = time.time()
            suite(result)  # 执行测试
            duration = round(time.time() - start_time, 6)
            print(len(result.success), len(result.failures))
            # 渲染数据到模板
            content = Template(TPL).render({"title": self.title,
                                            "description": self.description,
                                            "cases": result.cases,
                                            "run_num": result.testsRun,
                                            "pass_num": len(result.success),
                                            "fail_num": len(result.failures),
                                            "skipped_num": len(result.skipped),
                                            "error_num": len(result.errors),
                                            "duration": duration})
            with open(self.file, "w") as f:
                f.write(content)  # 写入文件
            return result
    
    
    if __name__ == "__main__":
        suite = unittest.defaultTestLoader.discover("./")
        HTMLRunner(output="report.html",
                   title="测试报告",
                   description="测试报告描述").run(suite)
    
    

    示例测试报告

    Python测试交流,欢迎添加作者微信:lockingfree

  • 相关阅读:
    springboot事物和事物回滚
    MyBatis Sql语句中的转义字符
    使用 vagrant新建Linux虚拟机
    Centos 6.7 安装mongodb
    阿里云windows server 2012 TIME_WAIT CLOSE_WAIT
    使用Eclipse打jar包 包含依赖jar包
    linux crontab定时器
    mysql 存储过程学习笔记
    oracle windows 新建用户授权 导出导入bmp文件
    解决hash冲突的方法
  • 原文地址:https://www.cnblogs.com/superhin/p/11454776.html
Copyright © 2020-2023  润新知