之前已经介绍了airTest的原理,该文主要指引大家能够将airTest框架应用到具体的测试项目当中去。
首先要考虑的是:
1. 你是用airTest 去做什么自动化 (android, ios, web)
2. airTest 能做什么,不能做什么,然后我们需要做出什么优化?
通过实际的使用,我其实发现airTest最大的优点是在元素识别方面,能够让没有编码基础或者是编码能力比较弱的人也可以编写自动化测试脚本。
但是大家使用的时候也会发现airTest没有良好的用例设计、管理机制。 没有很好的参数管理,同时一个air文件会生成一个测试报告,没有报告聚合的功能。
特别适用于: 通过外包执行功能测试的情况。 外包只要帮你录入脚本就行了。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
我们需要优化的几个地方:
1. 提供报告聚合功能,一次可以看多个用例的执行情况
2. Log聚合功能
3. 良好的用例设计/参数管理/方法封装的功能。
4. 批量执行脚本的功能
批量执行脚本的代码: (loggin.conf文件可以自行配置,这里不展开。)
import time from airtest.cli.runner import AirtestCase, run_script from argparse import * import shutil import os import logging.config logging.config.fileConfig('../logging.conf') logger = logging.getLogger('root') # alltestnames = allcase_list_new.case_list() # alltestnames = ['webDemo.air', 'webDemo2.air'] # logger.info(alltestnames) conf_root_dir = 'C:\\Python项目\\AirtestIDE_2019-01-15_py3_win64\\demo\\AirtestCase-master\\' def init_log_folder(): """初始化日志根目录""" name = time.strftime("log_%Y%m%d_%H%M%S", time.localtime()) if not os.path.exists(name): os.mkdir(name) print("creat file_dir: ", name) return namedef del_file(file_path): for name in os.listdir(file_path): if name.startswith("log_20"): try: shutil.rmtree(name) except Exception as e: print('filepath: ', file_path) print("del failed !!", e) def copy_file(olddir_path, newdir_path): # 遍历路径内的文件,只是一层 for name in os.listdir(olddir_path): if name.endswith(".txt"): # 只复制特定类型文件 # print (os.path.join(root, name)) source = os.path.join(olddir_path, name) target = os.path.join(newdir_path, name) try: shutil.copy(source, target) # name.close() except: print("Copy %s failed!" % name) class CustomAirtestCase(AirtestCase): def setUp(self): logger.info("custom setup") super(CustomAirtestCase, self).setUp() def tearDown(self): logger.info("custom tearDown") super(CustomAirtestCase, self).setUp() def run_air(self, root_dir, device): for f in os.listdir(root_dir): if f.endswith(".air"): # f为.air案例名称:银行.air script = os.path.join(root_dir, f) logger.info('执行脚本 :' + script) # 日志存放路径和名称 # log = os.path.join(root_dir, + airName) # logger.info('用例log保存文件夹=' + log) logdir = os.path.join(conf_root_dir, init_log_folder()) if os.path.isdir(logdir): shutil.rmtree(logdir) else: logger.info('日志路径: ' + logdir) # output_file = log + '\\' + 'log.html' args = Namespace(device=device, log=logdir, recording=None, script=script) try: run_script(args, AirtestCase) # 将log和截图文件等复制到脚本文件下,方便生成报告 copy_file(logdir, script) except Exception as e: logger.exception(str(e)) pass if __name__ == '__main__': test = CustomAirtestCase() # device = ['android:2d87aa41'] # device = ['android:127.0.0.1:62001'] device = ["windows:///"] # for d in device: test.run_air(conf_root_dir + '用例集', device)
聚合报告的代码:
# -*- coding: utf-8 -*- import os import io import types import shutil import json import jinja2 from airtest.utils.compat import decode_path import airtest.report.report as R HTML_FILE = "log.html" HTML_TPL = "log_template.html" STATIC_DIR = os.path.dirname(R.__file__) def get_parger(ap): ap.add_argument("script", help="script filepath") ap.add_argument("--outfile", help="output html filepath, default to be log.html") ap.add_argument("--static_root", help="static files root dir") ap.add_argument("--log_root", help="log & screen data root dir, logfile should be log_root/log.txt") ap.add_argument("--record", help="custom screen record file path", nargs="+") ap.add_argument("--export", help="export a portable report dir containing all resources") ap.add_argument("--lang", help="report language", default="en") ap.add_argument("--plugins", help="load reporter plugins", nargs="+") return ap def get_script_info(script_path): script_name = os.path.basename(script_path) result_json = {"name": script_name, "author": None, "title": script_name, "desc": None} return json.dumps(result_json) def _make_export_dir(self): dirpath = self.script_root logpath = self.script_root # copy static files for subdir in ["css", "fonts", "image", "js"]: dist = os.path.join(dirpath, "static", subdir) shutil.rmtree(dist, ignore_errors=True) self.copy_tree(os.path.join(STATIC_DIR, subdir), dist) return dirpath, logpath def report(self, template_name, output_file=None, record_list=None): """替换LogToHtml中的report方法""" self._load() steps = self._analyse() # 修改info获取方式 info = json.loads(get_script_info(self.script_root)) if self.export_dir: self.script_root, self.log_root = self._make_export_dir() output_file = os.path.join(self.script_root, HTML_FILE) self.static_root = "static/" if not record_list: record_list = [f for f in os.listdir(self.log_root) if f.endswith(".mp4")] records = [os.path.join(self.log_root, f) for f in record_list] if not self.static_root.endswith(os.path.sep): self.static_root = self.static_root.replace("\\", "/") self.static_root += "/" data = {} data['steps'] = steps data['name'] = os.path.basename(self.script_root) data['scale'] = self.scale data['test_result'] = self.test_result data['run_end'] = self.run_end data['run_start'] = self.run_start data['static_root'] = self.static_root data['lang'] = self.lang data['records'] = records data['info'] = info return self._render(template_name, output_file, **data) def get_result(self): return self.test_result def main(args): # script filepath path = decode_path(args.script) record_list = args.record or [] log_root = decode_path(args.log_root) or path static_root = args.static_root or STATIC_DIR static_root = decode_path(static_root) export = decode_path(args.export) if args.export else None lang = args.lang if args.lang in ['zh', 'en'] else 'zh' plugins = args.plugins # gen html report rpt = R.LogToHtml(path, log_root, static_root, export_dir=export, lang=lang, plugins=plugins) # override methods rpt._make_export_dir = types.MethodType(_make_export_dir, rpt) rpt.report = types.MethodType(report, rpt) rpt.get_result = types.MethodType(get_result, rpt) rpt.report(HTML_TPL, output_file=args.outfile, record_list=record_list) return rpt.get_result() if __name__ == "__main__": import argparse ap = argparse.ArgumentParser() args = get_parger(ap).parse_args() basedir = os.path.dirname(os.path.realpath(__file__)) logdir = os.path.realpath(args.script) # 聚合结果 results = [] # 遍历所有日志 for subdir in os.listdir(logdir): if os.path.isfile(os.path.join(logdir, subdir)): continue args.script = os.path.join(logdir, subdir) args.outfile = os.path.join(args.script, HTML_FILE) result = {} result["name"] = subdir result["result"] = main(args) results.append(result) # 生成聚合报告 env = jinja2.Environment( loader=jinja2.FileSystemLoader(basedir), extensions=(), autoescape=True ) print("path: ",basedir) template = env.get_template("summary_template.html",basedir) html = template.render({"results": results}) output_file = os.path.join(logdir, "summary.html") with io.open(output_file, 'w', encoding="utf-8") as f: f.write(html) print(output_file)
最终实现过程:
1. 外包测试人员通过airTest的IDE录制脚本用例文件.air, 放入到测试服务器指定的 用例集 目录下
2. 执行测试,生成聚合测试报告
3. 分析,并不断优化。(最好是结合jekins做一个持续集成的闭环)
4. 期待airTest有更好的开源功能