First of all, I was trying to build a test automation project in which cases were managed by built-in unittest in Python 3. Since unittest has no dependency support (as far as I know), I wrote my own decorator to support this:
def depends_on(case=''):
"""dependcy decorator"""
if not case:
raise DependencyError
_mark = []
def wrap_func(func):
@wraps(func)
def inner_func(self):
# check recursive
if case == func.__name__:
raise DependencyError(1)
_r = self._outcome.result
_f, _e, _s = _r.failures, _r.errors, _r.skipped
# check result before
if not (_f or _e or _s):
return func(self)
if _f:
_mark.extend([fail[0] for fail in _f])
if _e:
_mark.extend([error[0] for error in _e])
if _s:
_mark.extend([skip[0] for skip in _s])
unittest.skipIf(
case in str(_mark),
f'The pre-depend case :{case} has failed! Skip the specified case!'
)(func)(self)
return inner_func
return wrap_func
and this decorator works fine after testing.
After these work, I want to parameterize my test_method in TestCase; so I wrote other decorators to support it like this:
def ddt(cls):
"""
:param cls: class which inherit from TestCase
:return:
"""
for name, func in list(cls.__dict__.items()):
if hasattr(func, "PARAMS"):
for index, case_data in enumerate(getattr(func, "PARAMS")):
new_test_name = _create_test_name(index, name)
if isinstance(case_data, dict) and case_data.get("title"):
test_desc = str(case_data.get("title"))
elif isinstance(case_data, dict) and case_data.get("desc"):
test_desc = str(case_data.get("desc"))
elif (not isinstance(case_data, str)) and hasattr(case_data, 'title'):
test_desc = str(case_data.title)
else:
test_desc = func.__doc__
# _update_func will change func's name by add postfix(which is number index)
func2 = _update_func(new_test_name, case_data, test_desc, func)
setattr(cls, new_test_name, func2)
else:
delattr(cls, name)
return cls
def list_data(datas):
"""
:param datas: testing data set
:return:
"""
def wrapper(func):
setattr(func, "PARAMS", datas)
return func
return wrapper
In this code block, I defined two methods: list_data will attach data to test func in TestCase, and ddt will check if a method in the class has the attribute PARAMS, and change its name by adding a numeric index. For example: if the method name is test_demo, it will produce test_demo01 and test_demo02, and will delete test_demo after all of this.
The problem is that sometimes, a parameterized method needs to depend on another method, which will make me use two decorators like this:
@ddt
class TestMyCase(unittest.TestCase)
@depends_on('test_add_protocol')
@list_data([
{'a': 1},
{'b': 2}
])
def test_delete_protocol(self, data):
...
After doing this, it will happen that depends_on stops working. I have realized that the inner decorator will be called first (which means test_delete_protocol will be deleted before depends_on works), and I think that's why it doesn't behave as I expected.
How can I fix it? I have tried to make depends_on inner, but that also doesn't work.