iTesting,爱测试,爱分享
前面我们讲了什么是python数据驱动,如何使用及如何实现数据驱动。没看过文章的请移步: Python数据驱动实践(一)–ddt实现数据驱动 Python数据驱动实践(二)–教你用Python实现数据驱动
今天我们来解决另外一个问题,实现数据驱动后,如何在测试报告里体现?
老规矩,先上段代码:
# 这段代码实现了一个事情, 测试一个sum_data的方法。
from functools import wraps
def data_provider(test_data):
def wrapper(func):
@wraps(func)
def repl(self, *args, **kwargs):
for i in test_data:
print('func starts')
func(self, *i, **kwargs)
print('func ends')
return repl
return wrapper
class SumData:
def __init__(self):
pass
def sum_data(self, *ags):
return sum(ags)
class TestSumData:
def __init__(self):
pass
@data_provider([(1, 2, 3), (4, 5, 9)])
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
if __name__ == "__main__":
print(TestSumData().test_sum_data())
#唯一需要注意的是,@wraps的作用:
#由于装饰器的加入导致Python解释器认为函数本身发生了改变,
#所以用@wraps,
#它可以将原函数对象的指定属性复制给包装函数对象,
#保证装饰器不会对被装饰函数造成影响
我们知道,运行测试用例时候,通常会给一个待运行函数的集合,针对集合的每一个函数,逐个运行一遍,然后收集每个测试函数(用例)的名字,运行结果,然后保存,待生成测试报告用。
我们当前的代码, 根据data provider提供的数据不同,test_sum_data 这个函数也会运行多次,但是测试报告里只有一个test_sum_data, 那么如何解决呢? 1.先检查要运行的测试函数有没有data provider。 2.对于每个有data provider的测试函数,针对每一条数据,生成一个新的名字。 3.把新的名字加入到要运行的test case列表里。
根据这个我们知道,我们代码少两个函数实现, 一个是检查函数有没有data provider, 二是给有data provider装饰的函数生成新名字。怎么实现,当然是装饰器啦。 那么如何检查函数有没有提供data呢?我们可以人为给提供了data的函数加些属性。
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
如何给有__data__provider __ 属性的函数生成新名字呢?
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 get_test_cases(cls):
return_cases = []
for name, func in list(cls.__dict__.items()):
if hasattr(func, '__data_Provider__'):
for i, v in enumerate(getattr(func, "__data_Provider__")):
test_name = mk_test_name(name, getattr(v, "__name__", v), i)
return_cases.append((test_name, func, v))
return return_cases
最后我们整理下,看看实际上是如何使用的。
import re
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
class SumData:
def __init__(self):
pass
def sum_data(self, *ags):
return sum(ags)
class TestSumData:
def __init__(self):
pass
@data_provider([(1, 2, 3), (4, 5, 6)])
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
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 get_test_cases(cls):
return_cases = []
for name, func in list(cls.__dict__.items()):
if hasattr(func, '__data_Provider__'):
for i, v in enumerate(getattr(func, "__data_Provider__")):
test_name = mk_test_name(name, getattr(v, "__name__", v), i)
return_cases.append((test_name, func, v))
return return_cases
if __name__ == "__main__":
cases_to_run = []
cases_run_success = []
cases_run_fail = []
#注1
cases_to_run = get_test_cases(TestSumData)
while cases_to_run:
try:
case = cases_to_run.pop(0)
name = case[0]
func = case[1]
v = case[2]
func(name, *v)
except:
# traceback.print_exc()
cases_run_fail.append(name)
else:
cases_run_success.append(name)
print(cases_run_success)
print(cases_run_fail)
#output
the value are 1, 2, 3
the value are 4, 5, 6
['test_sum_data_1__1__2__3_']
['test_sum_data_2__4__5__6_']
我们定义并维护了三个列表,一个是cases_to_run,放我们找到的所有的测试用例,一个是cases_run_success,放运行成功的用例,最后一个 cases_run_fail, 放测试不成功的用例。
从运行结果我们可以看出, 我们只有一个测试函数test_sum_data,测试报告里却有了两个用例test_sum_data112__3, test_sum_data245__6。
就这样我们就实现了测试报告里数据驱动可视化。
Hold on, 看我上面代码的注释(注1),我们传入的是固定的测试类,那么在真实的测试中,我希望框架去自动查找测试类和测试方法,这个怎么搞?
请期待下次分享 :)