iTesting,爱测试,爱分享
前面几天, 我从数据驱动的一个第3方库ddt出发,连续分享了3篇文章: Python数据驱动实践(一)–ddt实现数据驱动 Python数据驱动实践(二)–教你用Python实现数据驱动 Python数据驱动实践(三)–动态添加测试用例
后面两篇文章实际上是任何一个测试框架都必须要有的部分。 今天我再分享一篇如何动态挑选测试用例, 大家知道,自动化脚本越写越多,但不是每次都需要full regression, 这个时候需要把开发修改涉及到的测试用例跑一下,而那些无关的用例可以不跑。
那么,思路是什么? 如果对每一个用例,我定义的时候给一个标签比如说Test,再给它一个值,True或False,这样我框架寻找测试用例的时候就找标签编辑为Test且值是True的就好了。 老规矩,先上代码:
# test_decorator.py
def TestClass(enabled=True):
def wraps(cls):
setattr(cls, "__test_type__", "TestClass")
setattr(cls, "__enabled__", enabled)
return cls
return wraps
def Test(enabled= True):
def wraps(func):
setattr(func, "__test_type__", "Test")
setattr(func, "__enabled__", enabled)
return func
return wraps
# 利用装饰器,对于每一个进来的类或func,
#我们给它加上一个属性一个值,然后再你需要运行的类或者func上装饰就好了。
结合我们上次讲过的动态添加测试用例, 和数据驱动,我们把这部分整合起来看看,一个简单完整的测试框架如下。 1.从指定的文件夹/文件下查找待运行测试类/方法 2.找到待运行测试类/方法,并根据数据不同重新生成测试用例 3.运行测试用例集并保存运行结果
我的整个项目层次结构是这样的:
其中: Common:
放通用的功能,比如,查找待测试用例的test_case_finder, 和我们上文的定义是否测试类/测试函数的装饰器test_decorator
pages:
放我们所有的页面功能,如果是UI测试,这个就对应于一个个的UI Page,我们将以Page Object组织页面元素,并定义页面类和方法,每一个页面当作一个page看待并定义一个类
tests:
这个下面放我们的测试用例,结构跟pages完全对应,每一个测试类对应于一个page 类。
run:
这个文件定义了用例如何运行,是并行还是顺序。
还有其它的很多功能,我们暂且不讲,先来看看这几个如何协作的。
# pages/page_add.py
#我们就定义个多数相加的方法。
class SumData:
def __init__(self):
pass
def sum_data(self, *ags):
return sum(ags)
再看它对应的测试类
# tests/test_page1/test_page_add.py
#这里面我定义了两个测试方法,一个要参数,一个不要。
#并且用了两个装饰类,一个用来提供测试数据,一个用来标记是否需要测试
from common.test_case_finder import data_provider
from common.test_decorator import Test
from pages.page_add import SumData
class TestSumData:
def __init__(self):
pass
@data_provider([(1, 2, 3), (4, 5, 9)])
@Test()
def test_sum_data(self, x, y, z):
print("the value are {0}, {1}, {2}".format(x, y, z))
assert SumData().sum_data(x, y) == z
@Test(enabled=False)
def test_sum_data2(self):
print(2)
assert SumData().sum_data(4, 5) == 7
这两个装饰类的定义Test()我们前面已经介绍过了,test provider定义如下:
def data_provider(test_data):
def wrapper(func):
setattr(func, "__data_Provider__", test_data)
global index_len
index_len = len(str(len(test_data)))
return func
return wrapper
好了,有了page类,有个test类,那么如何让我们的程序查找到我们需要运行的测试用例呢?
#test_case_finder.py
# 简化下,只给出如何鉴别待运行的用例
#mod_ref就是我们动态加载进来的所有测试module。
def find_classes_in_module(self, mod_ref):
test_classes = []
for module in mod_ref:
#微信关注公众号iTesting,加入万人测试团
cls_members = inspect.getmembers(module, inspect.isclass)
for cls in map(lambda x: x[1], cls_members):
for name, func in list(cls.__dict__.items()):
if hasattr(func, "__test_type__") and hasattr(func, "__enabled__"):
if getattr(func, "__test_type__") == "Test" and getattr(func, "__enabled__") == True:
test_classes.append(func)
return test_classes
那么,用例找出来了,如何数据驱动呢?
#test_case_finder.py
def mk_test_name(name, value, index=0):
index = "{0:0{1}}".format(index+1, index_len)
test_name = "{0}_{1}_{2}".format(name, index, value)
return re.sub(r'\W|^(?=\d)', '_', test_name)
def unpack_test_cases_from_functions(func_list):
return_cases = []
for func in func_list:
if hasattr(func, '__data_Provider__'):
for i, v in enumerate(getattr(func, "__data_Provider__")):
test_name = mk_test_name(func.__name__, getattr(v, "__name__", v), i)
return_cases.append((test_name, func, v))
else:
return_cases.append((func.__name__, func, None))
return return_cases
#我们通过反射的方式找到有Test()装饰的测试方法,并且只把enabled=True的测试方法放入我们的测试用例里。
测试用例也找到了,该运行了:
#run.py
#简化版, 实现了一个简单的顺序执行,我们后面将一步步优化至并发,顺序都可以
import traceback
from common.test_case_finder import DiscoverTestCases, unpack_test_cases_from_functions
if __name__ == "__main__":
#下面一句给定了查找测试用例的根目录,如果不给定,我们默认从tests文件夹找
mypath = r"D:\ktest\tests\test_page1"
cases_to_run = []
cases_run_success = []
cases_run_fail = []
discover_cases = DiscoverTestCases(mypath)
mds = discover_cases.get_modules_spec()
cases_to_run = unpack_test_cases_from_functions(discover_cases.find_classes_in_module(mds))
while cases_to_run:
try:
name, func, value = cases_to_run.pop(0)
if value:
func.__call__(name, *value)
else:
func.__call__(name)
except:
# traceback.print_exc()
cases_run_fail.append(name)
else:
cases_run_success.append(name)
print('Below cases are passed:\n %s' %cases_run_success)
print('Below cases are failed:\n %s' % cases_run_fail)
#以上给定了一个简单的方法,找到所有的呆测试用例,
#然后一个个执行,并且将测试成功和失败用例集分别存放,方便后续测试报告的生成。
运行结果如下:
Below cases are passed:
['test_sum_data_1__1__2__3_', 'test_sum_data_2__4__5__9_']
Below cases are failed:
[]
基本上实现了我们的需求,但是一个测试框架,要考虑的还很多,比如: 1.测试fixture,包括运行前准备和运行后清理。 2.并发执行 3.测试报告 4.邮件发送 5.错误截图 6.log记录
我后续将带你逐个击破,最终写出自己的测试框架,敬请期待。