iTesting软件测试知识分享

测试框架实践--动态挑选待运行测试用例

前面几天, 我从数据驱动的一个第3方库ddt出发,连续分享了3篇文章:
测试框架–教你用Python实现数据驱动1
测试框架–教你用Python实现数据驱动2
测试框架–数据驱动动态增加测试用例
后面两篇文章实际上是任何一个测试框架都必须要有的部分。 今天我再分享一篇如何动态挑选测试用例, 大家知道,自动化脚本越写越多,但不是每次都需要full regression, 这个时候需要把开发修改涉及到的测试用例跑一下,而那些无关的用例可以不跑。

那么,思路是什么? 如果对每一个用例,我定义的时候给一个标签比如说Test,再给它一个值,True或False,这样我框架寻找测试用例的时候就找标签编辑为Test且值是True的就好了。
老规矩,先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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
这个文件定义了用例如何运行,是并行还是顺序。

还有其它的很多功能,我们暂且不讲,先来看看这几个如何协作的。

1
2
3
4
5
6
7
# pages/page_add.py
#我们就定义个多数相加的方法。
class SumData:
def __init__(self):
pass
def sum_data(self, *ags):
return sum(ags)

再看它对应的测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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定义如下:
1
2
3
4
5
6
7
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类,那么如何让我们的程序查找到我们需要运行的测试用例呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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

那么,用例找出来了,如何数据驱动呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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的测试方法放入我们的测试用例里。

测试用例也找到了,该运行了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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)
#以上给定了一个简单的方法,找到所有的呆测试用例,
#然后一个个执行,并且将测试成功和失败用例集分别存放,方便后续测试报告的生成。

运行结果如下:
1
2
3
4
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记录

我后续将带你逐个击破,最终写出自己的测试框架,敬请期待。

🐶 您的支持将鼓励我继续创作 🐶
-------------评论, 吐槽, 学习交流,请关注微信公众号 iTesting-------------
请关注微信公众号 iTesting wechat
扫码关注,跟作者互动

本文标题:测试框架实践--动态挑选待运行测试用例

文章作者:请关注微信公众号 iTesting

发布时间:2018年11月13日 - 23:11

最后更新:2018年12月28日 - 00:12

原始链接:http://www.helloqa.com/2018/11/13/测试框架/测试框架实践--动态挑选待运行测试用例/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。