之前已经介绍了airTest的原理,该文主要指引大家能够将airTest框架应用到具体的测试项目当中去。

首先要考虑的是:

1. 你是用airTest 去做什么自动化 (android, ios, web)

2. airTest 能做什么,不能做什么,然后我们需要做出什么优化?

通过实际的使用,我其实发现airTest最大的优点是在元素识别方面,能够让没有编码基础或者是编码能力比较弱的人也可以编写自动化测试脚本。

但是大家使用的时候也会发现airTest没有良好的用例设计、管理机制。 没有很好的参数管理,同时一个air文件会生成一个测试报告,没有报告聚合的功能。

特别适用于:  通过外包执行功能测试的情况。 外包只要帮你录入脚本就行了。

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

我们需要优化的几个地方:

1. 提供报告聚合功能,一次可以看多个用例的执行情况

2. Log聚合功能

3. 良好的用例设计/参数管理/方法封装的功能。

4. 批量执行脚本的功能

批量执行脚本的代码: (loggin.conf文件可以自行配置,这里不展开。)

  1. import time
  2.  
  3. from airtest.cli.runner import AirtestCase, run_script
  4. from argparse import *
  5. import shutil
  6. import os
  7. import logging.config
  8.  
  9. logging.config.fileConfig('../logging.conf')
  10. logger = logging.getLogger('root')
  11. # alltestnames = allcase_list_new.case_list()
  12. # alltestnames = ['webDemo.air', 'webDemo2.air']
  13. # logger.info(alltestnames)
  14. conf_root_dir = 'C:\\Python项目\\AirtestIDE_2019-01-15_py3_win64\\demo\\AirtestCase-master\\'
  15.  
  16. def init_log_folder():
  17. """初始化日志根目录"""
  18. name = time.strftime("log_%Y%m%d_%H%M%S", time.localtime())
  19. if not os.path.exists(name):
  20. os.mkdir(name)
  21. print("creat file_dir: ", name)
  22. return namedef del_file(file_path):
  23. for name in os.listdir(file_path):
  24. if name.startswith("log_20"):
  25. try:
  26. shutil.rmtree(name)
  27. except Exception as e:
  28. print('filepath: ', file_path)
  29. print("del failed !!", e)
  30.  
  31. def copy_file(olddir_path, newdir_path):
  32. # 遍历路径内的文件,只是一层
  33. for name in os.listdir(olddir_path):
  34. if name.endswith(".txt"): # 只复制特定类型文件
  35. # print (os.path.join(root, name))
  36. source = os.path.join(olddir_path, name)
  37. target = os.path.join(newdir_path, name)
  38. try:
  39. shutil.copy(source, target)
  40. # name.close()
  41. except:
  42. print("Copy %s failed!" % name)
  43.  
  44. class CustomAirtestCase(AirtestCase):
  45. def setUp(self):
  46. logger.info("custom setup")
  47. super(CustomAirtestCase, self).setUp()
  48.  
  49. def tearDown(self):
  50. logger.info("custom tearDown")
  51. super(CustomAirtestCase, self).setUp()
  52.  
  53. def run_air(self, root_dir, device):
  54. for f in os.listdir(root_dir):
  55. if f.endswith(".air"):
  56. # f为.air案例名称:银行.air
  57. script = os.path.join(root_dir, f)
  58. logger.info('执行脚本 :' + script)
  59. # 日志存放路径和名称
  60. # log = os.path.join(root_dir, + airName)
  61. # logger.info('用例log保存文件夹=' + log)
  62. logdir = os.path.join(conf_root_dir, init_log_folder())
  63. if os.path.isdir(logdir):
  64. shutil.rmtree(logdir)
  65. else:
  66. logger.info('日志路径: ' + logdir)
  67.  
  68. # output_file = log + '\\' + 'log.html'
  69. args = Namespace(device=device, log=logdir, recording=None, script=script)
  70. try:
  71. run_script(args, AirtestCase)
  72. # 将log和截图文件等复制到脚本文件下,方便生成报告
  73. copy_file(logdir, script)
  74. except Exception as e:
  75. logger.exception(str(e))
  76. pass
  77.  
  78. if __name__ == '__main__':
  79. test = CustomAirtestCase()
  80. # device = ['android:2d87aa41']
  81. # device = ['android:127.0.0.1:62001']
  82. device = ["windows:///"]
  83. # for d in device:
  84. test.run_air(conf_root_dir + '用例集', device)

聚合报告的代码:

  1. # -*- coding: utf-8 -*-
  2.  
  3. import os
  4. import io
  5. import types
  6. import shutil
  7. import json
  8. import jinja2
  9. from airtest.utils.compat import decode_path
  10. import airtest.report.report as R
  11.  
  12. HTML_FILE = "log.html"
  13. HTML_TPL = "log_template.html"
  14. STATIC_DIR = os.path.dirname(R.__file__)
  15.  
  16. def get_parger(ap):
  17. ap.add_argument("script", help="script filepath")
  18. ap.add_argument("--outfile", help="output html filepath, default to be log.html")
  19. ap.add_argument("--static_root", help="static files root dir")
  20. ap.add_argument("--log_root", help="log & screen data root dir, logfile should be log_root/log.txt")
  21. ap.add_argument("--record", help="custom screen record file path", nargs="+")
  22. ap.add_argument("--export", help="export a portable report dir containing all resources")
  23. ap.add_argument("--lang", help="report language", default="en")
  24. ap.add_argument("--plugins", help="load reporter plugins", nargs="+")
  25. return ap
  26.  
  27. def get_script_info(script_path):
  28. script_name = os.path.basename(script_path)
  29. result_json = {"name": script_name, "author": None, "title": script_name, "desc": None}
  30. return json.dumps(result_json)
  31.  
  32. def _make_export_dir(self):
  33. dirpath = self.script_root
  34. logpath = self.script_root
  35. # copy static files
  36. for subdir in ["css", "fonts", "image", "js"]:
  37. dist = os.path.join(dirpath, "static", subdir)
  38. shutil.rmtree(dist, ignore_errors=True)
  39. self.copy_tree(os.path.join(STATIC_DIR, subdir), dist)
  40.  
  41. return dirpath, logpath
  42.  
  43. def report(self, template_name, output_file=None, record_list=None):
  44. """替换LogToHtml中的report方法"""
  45. self._load()
  46. steps = self._analyse()
  47. # 修改info获取方式
  48. info = json.loads(get_script_info(self.script_root))
  49.  
  50. if self.export_dir:
  51. self.script_root, self.log_root = self._make_export_dir()
  52. output_file = os.path.join(self.script_root, HTML_FILE)
  53. self.static_root = "static/"
  54.  
  55. if not record_list:
  56. record_list = [f for f in os.listdir(self.log_root) if f.endswith(".mp4")]
  57. records = [os.path.join(self.log_root, f) for f in record_list]
  58.  
  59. if not self.static_root.endswith(os.path.sep):
  60. self.static_root = self.static_root.replace("\\", "/")
  61. self.static_root += "/"
  62.  
  63. data = {}
  64. data['steps'] = steps
  65. data['name'] = os.path.basename(self.script_root)
  66. data['scale'] = self.scale
  67. data['test_result'] = self.test_result
  68. data['run_end'] = self.run_end
  69. data['run_start'] = self.run_start
  70. data['static_root'] = self.static_root
  71. data['lang'] = self.lang
  72. data['records'] = records
  73. data['info'] = info
  74.  
  75. return self._render(template_name, output_file, **data)
  76.  
  77. def get_result(self):
  78. return self.test_result
  79.  
  80. def main(args):
  81. # script filepath
  82. path = decode_path(args.script)
  83. record_list = args.record or []
  84. log_root = decode_path(args.log_root) or path
  85. static_root = args.static_root or STATIC_DIR
  86. static_root = decode_path(static_root)
  87. export = decode_path(args.export) if args.export else None
  88. lang = args.lang if args.lang in ['zh', 'en'] else 'zh'
  89. plugins = args.plugins
  90.  
  91. # gen html report
  92. rpt = R.LogToHtml(path, log_root, static_root, export_dir=export, lang=lang, plugins=plugins)
  93. # override methods
  94. rpt._make_export_dir = types.MethodType(_make_export_dir, rpt)
  95. rpt.report = types.MethodType(report, rpt)
  96. rpt.get_result = types.MethodType(get_result, rpt)
  97.  
  98. rpt.report(HTML_TPL, output_file=args.outfile, record_list=record_list)
  99.  
  100. return rpt.get_result()
  101.  
  102. if __name__ == "__main__":
  103. import argparse
  104. ap = argparse.ArgumentParser()
  105. args = get_parger(ap).parse_args()
  106. basedir = os.path.dirname(os.path.realpath(__file__))
  107. logdir = os.path.realpath(args.script)
  108.  
  109. # 聚合结果
  110. results = []
  111.  
  112. # 遍历所有日志
  113. for subdir in os.listdir(logdir):
  114. if os.path.isfile(os.path.join(logdir, subdir)):
  115. continue
  116. args.script = os.path.join(logdir, subdir)
  117. args.outfile = os.path.join(args.script, HTML_FILE)
  118. result = {}
  119. result["name"] = subdir
  120. result["result"] = main(args)
  121. results.append(result)
  122.  
  123. # 生成聚合报告
  124. env = jinja2.Environment(
  125. loader=jinja2.FileSystemLoader(basedir),
  126. extensions=(),
  127. autoescape=True
  128. )
  129. print("path: ",basedir)
  130. template = env.get_template("summary_template.html",basedir)
  131. html = template.render({"results": results})
  132.  
  133. output_file = os.path.join(logdir, "summary.html")
  134. with io.open(output_file, 'w', encoding="utf-8") as f:
  135. f.write(html)
  136. print(output_file)

最终实现过程:

1. 外包测试人员通过airTest的IDE录制脚本用例文件.air, 放入到测试服务器指定的  用例集  目录下

2. 执行测试,生成聚合测试报告

3. 分析,并不断优化。(最好是结合jekins做一个持续集成的闭环)

4. 期待airTest有更好的开源功能

airTest 应用到项目并优化的更多相关文章

  1. 仿百度壁纸客户端(六)——完结篇之Gallery画廊实现壁纸预览已经项目细节优化

    仿百度壁纸客户端(六)--完结篇之Gallery画廊实现壁纸预览已经项目细节优化 百度壁纸系列 仿百度壁纸客户端(一)--主框架搭建,自定义Tab + ViewPager + Fragment 仿百度 ...

  2. 仿百度壁纸client(六)——完结篇之Gallery画廊实现壁纸预览已经项目细节优化

    仿百度壁纸client(六)--完结篇之Gallery画廊实现壁纸预览已经项目细节优化 百度壁纸系列 仿百度壁纸client(一)--主框架搭建,自己定义Tab + ViewPager + Fragm ...

  3. vue+webpack+element-ui项目打包优化速度与app.js、vendor.js打包后文件过大

    从开通博客到现在也没写什么东西,最近几天一直在研究vue+webpack+element-ui项目打包速度优化,想把这几天的成果记录下来,可能对前端牛人来说我这技术比较菜,但还是希望给有需要的朋友提供 ...

  4. 【Vuejs】335-(超全) Vue 项目性能优化实践指南

    点击上方"前端自习课"关注,学习起来~ 前言 Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 D ...

  5. vue大型项目高性能优化----想说爱你真的不容易

    一.背景   目前公司的电子合同采用表单设计器+合同业务配合实现,做了半年多后终于上线,但是下边员工普遍反映卡顿,甚至卡死,爆栈.尤其是新增和修改合同页面,因为这部分数据量大,逻辑复杂,很容易崩溃,所 ...

  6. 简记某WebGIS项目的优化之路

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1. 背景 该项目为研究生时的老师牵头,个人已毕业数年,应老师要求协助其 ...

  7. EF+MVC+cod First项目性能优化总结

    1.EF:this.Configuration.UseDatabaseNullSemantics = true; //关闭数据库null比较行为 2.实体必填字段要加:[Required]属性,可定长 ...

  8. iOS开发--项目内存优化

    在用非ARC模式编写iOS程序的时候,造成程序内存泄露在所难免,后期我们一般会进行内存优化.自己比较常用的内存优化方法有两种 1.Analyze,静态分析内存泄露的方法.很简单,在Xcode菜单栏中点 ...

  9. 小型 Web 页项目打包优化方案

    背景   目前团队中新的 Web 项目基本都采用了 Vue 或 React ,加上 RN,这些都属于比较重量级的框架,然而对于小型 Web 页面,又显得过大.早期的一些项目则使用了较原始的 HTML ...

随机推荐

  1. java.util.concurrent包下集合类的特点与适用场景

    java.util.concurrent包,此包下的集合都不允许添加null元素 序号 接口 类 特性 适用场景 1 Queue.Collection ArrayBlockingQueue 有界.阻塞 ...

  2. 干掉win10自带的不给力的应用(转自https://jingyan.baidu.com/article/08b6a591b7398514a8092238.html)

    1.右键以管理员身份运行Windows power shell,(怎么找到Windows power shell?按下win键,直接搜索就有了) 2.在应用中输入命令 Get-AppXPackage ...

  3. Kafka安装及开启SASL_PLAINTEXT认证(用户名和密码认证)

    前些日子要封装一个kafka的客户端驱动,配置了下kafka环境,发现配置复杂度完爆rabbitmq很多倍啊,而且发布订阅模式使用起来也很麻烦,可能就胜在分布式了吧. kafka需要java环境,自行 ...

  4. JS 实现兼容浏览器报警提示声音

    <!DOCTYPE HTML> <head> <title>JS实现报警提示音</title> <meta http-equiv="co ...

  5. python2操作MySQL

    #coding=utf-8   import MySQLdb   conn = MySQLdb.connect(host='localhost',user='root',passwd='123456' ...

  6. Rsync备份功能总结

    备份服务笔记====================================================================== Rsync是一款开源的.快速的.多功能的.可实 ...

  7. pgsql 常用命令

    1.连接到pgsql数据库 psql -U postgres 2.查看所有数据库 \l 3.连接到数据库test \c test 4.查看数据库所有表以及视图 \d 5.查看数据库所有的表 \dt 6 ...

  8. leetcode1035

    class Solution: def maxUncrossedLines(self, A: 'List[int]', B: 'List[int]') -> int: m = len(A) n ...

  9. 阿里云短信验证使用(PHP)

    1.登陆阿里云后台,事先添加签名和模板 2.使用composer下载阿里云SDK composer require alibabacloud/sdk 在PHP7.0下安装需要提前安装curl扩展 -c ...

  10. 深度学习原理与框架- tf.nn.atrous_conv2d(空洞卷积) 问题:空洞卷积增加了卷积核的维度,为什么不直接使用7*7呢

    空洞卷积, 从图中可以看出,对于一个3*3的卷积,可以通过使用增加卷积的空洞的个数,来获得较大的感受眼, 从第一幅图中可以看出3*3的卷积,可以通过补零的方式,变成7*7的感受眼,这里补零的个数为1, ...