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。

 

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

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

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

上一篇:解决python安装包无法正常安装问题


下一篇:剑指 Offer II 029. 排序的循环链表