ddt原理讲解
import unittest
import ddt
@ddt.ddt # 5.装饰整个类
class TestCase(unittest.TestCase):
@ddt.data((1, 2), (3, 4)) #1.执行data方法,返回值装饰test05
def test05(self, a):
print(a)
DATA_ATTR = '%values'
FILE_ATTR = '%file_path'
YAML_LOADER_ATTR = '%yaml_loader'
INDEX_LEN = '%index_len'
def data(*values):
return idata(values) # 2.调用idata 此时values是个元祖((1, 2), (3, 4))
def idata(iterable, index_len=None):
if index_len is None:
# Avoid consuming a one-time-use generator.
iterable = tuple(iterable) #((1, 2), (3, 4))
index_len = len(str(len(iterable))) #2
def wrapper(func): #4.调用 ,此时func指向test05,并为test05对象增加两属性
setattr(func, DATA_ATTR, iterable)
setattr(func, INDEX_LEN, index_len)
return func
return wrapper # 3.相当于warpper 装饰test05
def ddt(arg=None, **kwargs): #
print("传入的arg参数:",arg) # 6.arg指向类名TestCase
fmt_test_name = kwargs.get("testNameFormat", TestNameFormat.DEFAULT)
def wrapper(cls):
for name, func in list(cls.__dict__.items()):# 遍历类都属性
if hasattr(func, DATA_ATTR): # 被data装饰过的方法都有DATA_ATTR,且值为data的值元祖((1, 2), (3, 4))
index_len = getattr(func, INDEX_LEN) #2
for i, v in enumerate(getattr(func, DATA_ATTR)):
test_name = mk_test_name(
name, # 函数名
getattr(v, "__name__", v), #遍历data里面的值
i, #0
index_len, #1
fmt_test_name # 0
) # 改变测试用例的名字
test_data_docstring = _get_test_data_docstring(func, v)
if hasattr(func, UNPACK_ATTR):没有装饰@ddt.unpack,就过
if isinstance(v, tuple) or isinstance(v, list):
add_test(
cls,
test_name,
test_data_docstring,
func,
*v
)
else:
# unpack dictionary
add_test(
cls,
test_name,
test_data_docstring,
func,
**v
)
else:
add_test(cls, test_name, test_data_docstring, func, v) # 依次遍历传入(1,2),(3,4),所以测试用例只用一个参数接受
delattr(cls, name)
elif hasattr(func, FILE_ATTR):
file_attr = getattr(func, FILE_ATTR)
process_file_data(cls, name, func, file_attr)
delattr(cls, name)
return cls
return wrapper(arg) if inspect.isclass(arg) else wrapper # 调用wrapper
def add_test(cls, test_name, test_docstring, func, *args, **kwargs):
"""
Add a test case to this class.
"""
setattr(cls, test_name, feed_data(func, test_name, test_docstring,
*args, **kwargs)) # 给类新命名的函数名字增加返回值属性,返回值指向下面的wrapper
def feed_data(func, new_name, test_data_docstring, *args, **kwargs):
@wraps(func)
def wrapper(self):
return func(self, *args, **kwargs) # func被装饰函数test05的引用
wrapper.__name__ = new_name
wrapper.__wrapped__ = func
# set docstring if exists
if test_data_docstring is not None:
wrapper.__doc__ = test_data_docstring
else:
# Try to call format on the docstring
if func.__doc__:
try:
wrapper.__doc__ = func.__doc__.format(*args, **kwargs)
except (IndexError, KeyError):
# Maybe the user has added some of the formating strings
# unintentionally in the docstring. Do not raise an exception
# as it could be that user is not aware of the
# formating feature.
pass
return wrapper
总结
@ddt.data(参数)
# 参数为1,2,3这种,DATA_ATTR为(1,2,3)INDEX_LEN为3
# 参数为(1,2,3),DATA_ATTR为((1,2,3),)INDEX_LEN为1
# 参数为(1,2),(3,4)DATA_ATTR为((1,2),(3,4)) INDEX_LEN为2
setattr(func, DATA_ATTR, iterable)
setattr(func, INDEX_LEN, index_len)
给被装饰@ddt.unpack,给函数参数增加解包属性
# 参数为1,2,3这种,DATA_ATTR为(1,2,3) 遍历后1,2,3都是int类型,不可装饰uppack
# 参数为(1,2,3),DATA_ATTR为((1,2,3),)遍历后(1,2,3)可解包,被装饰函数接受3个参数,可以用*args接受
# 参数为(1,2),(3,4)DATA_ATTR为((1,2),(3,4)) 遍历后(1,2),(3,4)可解包,被装饰函数接受2个参数,可以用*args
# 参数为 {"name":"xiaowang","age":18},{"name":"xiaowang","age":18}遍历后 {"name":"xiaowang","age":18},{"name":"xiaowang","age":18}可解包,被装饰函数接受2个参数,可以用**kwargs
setattr(func, DATA_ATTR, iterable)
setattr(func, INDEX_LEN, index_len)
给类装饰@ddt.ddt,给函数参数增加解包属性