我们编写的用例不可能只有一个,执行起来也不可能是单个执行,需要批量执行用例,所有本片文档针对如何批量执行用例作说明。
1、项目结构确定,一般项目尽量按照固定结构来设计,有利于批量执行用例和项目用例的维护。项目的结构需要按照如下图所示的结构来设计,下图项目是我以前做的项目结构。
?
?批量执行用例在airtestIDE上是无法实现的,IDE只能单用例执行,所以这里通过脚本来实现。完整代码如下所示:
# -*- coding: utf-8 -*-
import unittest
import os
import sys
import six
import traceback
import types
import time
from io import open
from airtest.cli.parser import runner_parser
from airtest.core.api import G, auto_setup, log
from airtest.core.settings import Settings as ST
from airtest.utils.compat import decode_path
from copy import copy
class MyAirtestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.args = args
setup_by_args(args)
# setup script exec scope
cls.scope = copy(globals())
def setUp(self):
if self.args.log and self.args.recording:
for dev in G.DEVICE_LIST:
try:
dev.start_recording()
except:
traceback.print_exc()
# 设置日志路径
auto_setup(logdir=self._logdir)
def tearDown(self):
if self.args.log and self.args.recording:
for k, dev in enumerate(G.DEVICE_LIST):
try:
output = os.path.join(self.args.log, "recording_%d.mp4" % k)
dev.stop_recording(output)
except:
traceback.print_exc()
def runTest(self):
try:
# 调用脚本中的runCase方法并传递scope供脚本使用
self.runCase(self.scope)
except Exception as err:
tb = traceback.format_exc()
log("Final Error", tb)
six.reraise(*sys.exc_info())
@property
def logdir(self):
return self._logdir
@logdir.setter
def logdir(self, value):
self._logdir = value
def setup_by_args(args):
# init devices
if isinstance(args.device, list):
devices = args.device
elif args.device:
devices = [args.device]
else:
devices = []
print("do not connect device")
# set base dir to find tpl
args.script = decode_path(args.script)
# set log dir
if args.log is True:
print("save log in %s/log" % args.script)
args.log = os.path.join(args.script, "log")
elif args.log:
print("save log in '%s'" % args.log)
args.log = decode_path(args.log)
else:
print("do not save log")
# guess project_root to be basedir of current .air path
project_root = os.path.dirname(args.script) if not ST.PROJECT_ROOT else None
# 此处不设置日志路径,防止生成多余的log.txt
auto_setup(args.script, devices, None, project_root)
def new_case(py, logdir):
"""实例化MyAirtestCase并绑定runCase方法"""
with open(py, 'r', encoding="utf8") as f:
code = f.read()
obj = compile(code.encode("utf-8"), py, "exec")
ns = {}
ns["__file__"] = py
# exec obj in ns
exec(obj, ns)
func = ns["runCase"]
case = MyAirtestCase()
pyfilename = os.path.basename(py).replace(".py", "")
# 设置属性以便在setUp中设置日志路径
case.logdir = os.path.join(logdir, pyfilename)
# 绑定runCase方法
case.runCase = types.MethodType(func, case)
return case
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)
return name
def run_script(parsed_args, testcase_cls=MyAirtestCase):
global args # make it global deliberately to be used in MyAirtestCase & test scripts
args = parsed_args
dir = os.path.dirname(os.path.realpath(__file__))
suites = []
pys = []
# 获取所有用例集
for f in os.listdir(dir):
if f.endswith("用例集"):
f = os.path.join(dir, f)
if os.path.isdir(f):
suites.append(f)
# 获取所有脚本
for s in suites:
for f in os.listdir(s):
if f.endswith(".py") and not f.startswith("__"):
pys.append(os.path.join(s, f))
logdir = os.path.join(dir, init_log_folder())
args.log = logdir
suite = unittest.TestSuite()
# 添加脚本
for py in pys:
case = new_case(py, logdir)
suite.addTest(case)
result = unittest.TextTestRunner(verbosity=0).run(suite)
if not result.wasSuccessful():
sys.exit(-1)
if __name__ == "__main__":
ap = runner_parser()
args = ap.parse_args()
run_script(args, MyAirtestCase)
下面对逻辑进行说明:
2、继承TestCase实现测试用例类,run_script方法的测试类是MyAirtestCase,而MyAirtestCase继承unittest的TestCase类。其中setUpClass和setUp方法是用例执行前进行设备、参数的设置;runTest真正开始执行用例,tearDown方法在用例执行完毕后,输出log。
3、实例化MyAirtestCase,将项目下所有的py代码用例绑定到MyAirtestCase的runCase方法中,最后返回测试用例。
?4、遍历读取用例并将用例加入测试套件中,在run_script方法里面找到所有的脚本,然后将脚本添加到unittest的测试套件里面,最后通过执行测试套件来实现。下面是是核心的执行方法。附件中是全部runner代码。
?5、最后,通过main函数入库开始执行用例即可,其中runner_parser是airtest.cli.parser下运行时添加到log中的参数。实现的具体方法是runner_parser。
?runner_parser实现log参数数据设置。在run_scrpit中会有调用到。代码如下所示。
通过上面几步,就可以实现免IDE实现批量运行了,当然,如果想要多机器批量运行的话,此脚本还不能实现,需要另外实现多线程调度实现,这里先不做介绍,后续继续接受设置report报告。
?
|