• Airtest通过代码生成报告——simple_report、LogToHtml详解


    上期回顾:Airtest生成报告命令行airtest report详解


    以下基于
    python3.8;airtestIDE1.2.11;airtest1.2.2;pocoui1.0.83

    上期我们讲了在命令行生成报告,这次我们看下怎么通过脚本直接在代码中运行生成报告。分别是LogToHtml类和simple_report()函数。

    LogToHtml

    上期我们讲airtest report时,源码里已经看到了,最终生成报告,就是实例化LogToHtml类,并调用了里面的report()方法,那这次我们详细看下其源码。
    先看下LogToHtml类的初始化

     1# 文件位置:your_python_path/site-packages/airtest/report/report.py
    2class LogToHtml(object):
    3    """Convert log to html display """
    4    def __init__(self, script_root, log_root="", static_root="", export_dir=None, script_name="", logfile=None, lang="en", plugins=None):
    5        self.log = []
    6        self.script_root = script_root
    7        self.script_name = script_name
    8        if not self.script_name or os.path.isfile(self.script_root):
    9            self.script_root, self.script_name = script_dir_name(self.script_root)
    10        self.log_root = log_root or ST.LOG_DIR or os.path.join(".", DEFAULT_LOG_DIR)
    11        self.static_root = static_root or STATIC_DIR
    12        self.test_result = True
    13        self.run_start = None
    14        self.run_end = None
    15        self.export_dir = export_dir
    16        self.logfile = logfile or getattr(ST, "LOG_FILE", DEFAULT_LOG_FILE)
    17        self.lang = lang
    18        self.init_plugin_modules(plugins)

    参数说明:

    • script_root:脚本所在文件夹

    • log_root:log.txt所在文件夹

    • static_root:部署静态资源的服务器路径

    • export_dir:导出报告文件夹

    • script_name:脚本名称

    • logfile:log.txt的路径

    • lang:报告的语言,中文:zh;英文:en(默认)

    • plugins:插件,使用了poco框架,需要填写--plugin poco.utils.airtest.report。如果用到了airtest-selenium,需要填写`--plugin airtest_selenium.report

    源码解析:
    只说下重要的
    第8行:如果没有指定脚本名称,则使用script_dir_name()通过前面给的脚本所在文件夹自行获取

    第10行:指定了log.txt所在文件夹则直接赋值,否则使用全局变量ST.LOG_DIR给的路径(就是说你可以在代码最开始通过全局变量指定日志路径)。如果都没有,则取当前目录下的log文件夹。

    第16行:如果指定了log.txt全路径则直接赋值,否则使用全局变量ST.LOG_FILE给的路径(默认是log.txt,你可以在代码最开始通过全局变量指定日志全路径)

    LogToHtml类里有各种方法去获取生成报告所需的各种数据,这里我们只看一个我们直接接触到的:

     1    def _translate_desc(self, step, code):
    2        """ 函数描述 """
    3        if step['tag'] != "function":
    4            return None
    5        name = step['data']['name']
    6        res = step['data'].get('ret')
    7        args = {i["key"]: i["value"] for i in code["args"]}
    8
    9        desc = {
    10            "snapshot": lambda: u"Screenshot description: %s" % args.get("msg"),
    11            "touch": lambda: u"Touch %s" % ("target image" if isinstance(args['v'], dict) else "coordinates %s" % args['v']),
    12            "swipe": u"Swipe on screen",
    13            "wait": u"Wait for target image to appear",
    14            "exists": lambda: u"Image %s exists" % ("" if res else "not"),
    15            "text": lambda: u"Input text:%s" % args.get('text'),
    16            "keyevent": lambda: u"Click [%s] button" % args.get('keyname'),
    17            "sleep": lambda: u"Wait for %s seconds" % args.get('secs'),
    18            "assert_exists": u"Assert target image exists",
    19            "assert_not_exists": u"Assert target image does not exists",
    20        }
    21
    22        # todo: 最好用js里的多语言实现
    23        desc_zh = {
    24            "snapshot": lambda: u"截图描述: %s" % args.get("msg"),
    25            "touch": lambda: u"点击 %s" % (u"目标图片" if isinstance(args['v'], dict) else u"屏幕坐标 %s" % args['v']),
    26            "swipe": u"滑动操作",
    27            "wait": u"等待目标图片出现",
    28            "exists": lambda: u"图片%s存在" % ("" if res else u"不"),
    29            "text": lambda: u"输入文字:%s" % args.get('text'),
    30            "keyevent": lambda: u"点击[%s]按键" % args.get('keyname'),
    31            "sleep": lambda: u"等待%s秒" % args.get('secs'),
    32            "assert_exists": u"断言目标图片存在",
    33            "assert_not_exists": u"断言目标图片不存在",
    34        }
    35
    36        if self.lang == "zh":
    37            desc = desc_zh
    38
    39        ret = desc.get(name)
    40        if callable(ret):
    41            ret = ret()
    42        return ret

    看到了吗,我们看的报告中的步骤描述,就是在这里定义的,如果你想修改,可以直接修改这里。

    实例化LogToHtml类之后,就可以使用类中的方法report()生成报告了

     1    def report(self, template_name=HTML_TPL, output_file=HTML_FILE, record_list=None):
    2        """
    3        Generate the report page, you can add custom data and overload it if needed
    4
    5        :param template_name: default is HTML_TPL
    6        :param output_file: The file name or full path of the output file, default HTML_FILE
    7        :param record_list: List of screen recording files
    8        :return:
    9        """
    10        if not self.script_name:
    11            path, self.script_name = script_dir_name(self.script_root)
    12
    13        if self.export_dir:
    14            self.script_root, self.log_root = self._make_export_dir()
    15            # output_file可传入文件名,或绝对路径
    16            output_file = output_file if output_file and os.path.isabs(output_file) \
    17                else os.path.join(self.script_root, output_file or HTML_FILE)
    18            if not self.static_root.startswith("http"):
    19                self.static_root = "static/"
    20
    21        if not record_list:
    22            record_list = [f for f in os.listdir(self.log_root) if f.endswith(".mp4")]
    23        data = self.report_data(output_file=output_file, record_list=record_list)
    24        return self._render(template_name, output_file, **data)

    参数说明:

    • template_name:就是/your_python_path/site-packages/airtest/report/log_template.html,感兴趣可以打开看看以及看下源码。另外,你也可以传入自己的模板。

    • output_file:日志全路径,默认为log.html

    • record_list:录像文件列表

    源码解析:
    前面分别获取脚本路径和名称、导出文件和路径、录像列表。

    之后调用report_data()方法,该方法就是获取所有报告信息的,里面调用了LogToHtml类中的很多方法去获取生成报告所需的各种各样的数据,感兴趣的可以自己看看。

    最后调用并返回_render(),该方法其实就是用jinja2配合HTML模板生成报告(Jinja2是Python下的一个模板引擎,用来生成HTML网页)

    可以看下_render()的实现:

     1    @staticmethod
    2    def _render(template_name, output_file=None, **template_vars):
    3        """ 用jinja2渲染html"""
    4        env = jinja2.Environment(
    5            loader=jinja2.FileSystemLoader(STATIC_DIR),
    6            extensions=(),
    7            autoescape=True
    8        )
    9        env.filters['nl2br'] = nl2br
    10        env.filters['datetime'] = timefmt
    11        template = env.get_template(template_name)
    12        html = template.render(**template_vars)
    13
    14        if output_file:
    15            with io.open(output_file, 'w', encoding="utf-8") as f:
    16                f.write(html)
    17            LOGGING.info(output_file)
    18
    19        return html

    实例演示

     1__author__ = '公众号:测试工程师小站'
    2
    3from airtest.core.api import *
    4from airtest.report.report import LogToHtml
    5
    6auto_setup(__file__,logdir=True)
    7
    8touch([500, 500])  # 此一句代表整个脚本
    9
    10r = LogToHtml(script_root=r'D:\code\Airtest\test1.air', log_root=r'D:\code\Airtest\test1.air\log', export_dir=r'D:\code\Airtest\report\test1', lang='zh')
    11r.report()  # 用法1:生成报告就在导出文件夹下,名字为log.html
    12
    13# 用法2:所有的资源文件在导出文件夹,但HTML文件在report文件夹,且叫custom_report.html
    14# 个人不建议html文件与资源文件分离,放在一起不好管理吗?
    15# r.report(output_file=r'D:\code\Airtest\report\custom_report.html')

    将用例代码和生成报告代码写一起,有个缺点就是用例失败后,后面的代码就不会执行了,可以加try,改造后的代码

     1__author__ = '公众号:测试工程师小站'
    2
    3from airtest.core.api import *
    4from airtest.report.report import LogToHtml
    5
    6auto_setup(__file__,logdir=True)
    7try:
    8    touch([500, 500])  # 此一句代表整个脚本
    9except:
    10    print('发生异常,在这写异常处理语句,或是重新运行一遍脚本')
    11finally:  # 不管脚本成功与否,都会执行finally块中的语句
    12    r = LogToHtml(script_root=r'D:\code\Airtest\test1.air', log_root=r'D:\code\Airtest\test1.air\log', export_dir=r'D:\code\Airtest\report\test1', lang='zh')
    13    r.report()

    现在还有个问题,就是用代码生成报告,所有东西我们都写死了,这样每次报告都会相互覆盖,如果想保留每次报告,我们只需要将报告导出路径自定义一下,比如每次都导到一个新的文件夹,名字为:用例名_日期。

     1__author__ = '公众号:测试工程师小站'
    2
    3import time, os
    4from airtest.core.api import *
    5from airtest.report.report import LogToHtml
    6
    7auto_setup(__file__,logdir=True)
    8try:
    9    touch([500, 500])  # 此一句代表整个脚本
    10except:
    11    print('发生异常,在这写异常处理语句,或是重新运行一遍脚本')
    12finally:  # 不管脚本成功与否,都会执行finally块中的语句
    13    casename = os.path.basename(__file__)  # 用例名就取文件名,或者你也可以通过其他方式定义
    14    casename = casename.split('.')[0]
    15    dt = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
    16    dirname = casename + dt
    17    r = LogToHtml(script_root=r'D:\code\Airtest\test1.air', log_root=r'D:\code\Airtest\test1.air\log', export_dir=dirname, lang='zh')
    18    r.report()

    simple_report()

    simple_report()与LogToHtml类在同一个文件中,是一个独立的函数。

    1# 文件位置:your_python_path/site-packages/airtest/report/report.py
    2def simple_report(filepath, logpath=True, logfile=None, output=HTML_FILE):
    3    path, name = script_dir_name(filepath)
    4    if logpath is True:
    5        logpath = os.path.join(path, getattr(ST, "LOG_DIR", DEFAULT_LOG_DIR))
    6    rpt = LogToHtml(path, logpath, logfile=logfile or getattr(ST, "LOG_FILE", DEFAULT_LOG_FILE), script_name=name)
    7    rpt.report(HTML_TPL, output_file=output)

    参数说明:

    • filepath:对应的是LogToHtml初始化时的script_root参数,脚本文件的全路径,可以直接传入变量__file__

    • logpath:对应的是LogToHtml初始化时的log_root参数。布尔值,为True时,使用全局变量ST.LOG_DIR给的路径(就是说你可以在代码最开始通过全局变量指定日志路径),如果都没有,则取当前目录下的log文件夹

    • logfile:对应的是LogToHtml初始化时的logfile参数,log.txt的文件路径

    • output:对应的是report()方法中的output_file参数,报告导出全路径,必须以.html结尾

    源码解析:
    第1行获取脚本路径;
    第2、3行获取日志路径;
    第5行实例化LogToHtml;
    第6行调用LogToHtml类的report()方法生成报告。

    代码逻辑很简单,实际上调用的还是LogToHtml。但是使用simple_report()无法导出报告,只能在本地查看。

    实例演示
    我个人觉得simple_report()码如其名,就是给大家一个最简单的生成报告的方法。所以我个人觉得使用时就应该全用默认值参数:

     1__author__ = '公众号:测试工程师小站'
    2
    3from airtest.core.api import *
    4from airtest.report.report import simple_report
    5
    6auto_setup(__file__,logdir=True)
    7
    8touch([500, 500])  # 此一句代表整个脚本
    9 # 其他参数全用默认的,即在当前脚本路径下生成名为log.html的报告
    10simple_report(__file__)

    以上就是LogToHtml类和simple_report()函数的使用了。但如果你有意将自动化做的正规且容入项目流程中,那必然要涉及CI,比如用Jenkins自动执行。如果要自动执行,那就还是得用命令行调用的方法(airtest run、airtest report)。
    所以本篇内容不建议你实用,仅做为了解生成报告的内在逻辑学习,实用的还是看看前2期的airtest run、airtest report。

    下面再介绍官网提供的一个多次运行同一脚本,并每次生成报告的例子

    利用 while循环,重复执行3次完整的脚本内容,并依次生成 log1.html、log2.html 和 log3.html:

    a = 1
    while a < 4:
        # 执行完整的脚本内容
        pass

        # 生成报告
        from airtest.report.report import simple_report
        simple_report(__file__,logpath=True,output="log"+str(a)+".html")
        a = a + 1

    最终log查看窗打印的部分结果如下:

    也生成了3分独立的测试报告:

    ---------------------------------------------------------------------------------

    关注微信公众号即可在手机上查阅,并可接收更多测试分享~

  • 相关阅读:
    (转) 一步一步学习ASP.NET 5 (五)- TypeScript
    #一周五# win10通用平台,无处不在的Xamarin,msbuild开源,MVP卢建晖的Asp.NET 5系列 (视频)
    (转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性
    (转) 一步一步学习ASP.NET 5 (三)- 认识新的Web结构
    #winhec# 开发人员刷屏看点 (视频)
    (翻译) TFS源代码控制的未来 (TFSVC vs. Git)
    (转) 一步一步学习ASP.NET 5 (二)- 通过命令行和sublime创建项目
    【JS教程03】函数
    【JS教程02】变量、数据类型及基本语法规范
    【JS教程01】JavaScript介绍与页面嵌入方式
  • 原文地址:https://www.cnblogs.com/songzhenhua/p/15854502.html
Copyright © 2020-2023  润新知