之前已經介紹了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有更好的開源功能