import ddt
import unittest
from collections import namedtuple
@ddt.ddt
class TestExample(unittest.TestCase):
class DummyClass(namedtuple('DummyClass', ['name', 'arg1', 'arg2'])):
def __str__(self):
return self.name
@ddt.data(
DummyClass('TwoGreaterThanOne', 1, 2),
DummyClass('ThreeGreaterThanTwo', 2, 3)
)
def test_example(self, dummy):
self.assertGreater(dummy.arg2, dummy.arg1)
I see that some of this type of functionality is in the works for ddt 2.0, but I don't know what kind of progress has been made on that. In the meantime, I put together this helper module (below, tested successful in Python 2.7), which allows me to do this with a single decorator and may be helpful for others.
I am rather new at code development and contribution in Github, so am not sure of the best process to contribute this code and ensure it passes the necessary checks and tests.
import ddt
import unittest
class NamedDataList(list):
""" This is a helper class for @named_data that allows ddt tests to have meaningful names. """
def __init__(self, name, *args):
super(NamedDataList, self).__init__(args)
self.name = name
def __str__(self):
return str(self.name)
class NamedDataDict(dict):
""" This is a helper class for @named_data that allows ddt tests to have meaningful names. """
def __init__(self, name, **kwargs):
super(NamedDataDict, self).__init__(kwargs)
self.name = name
def __str__(self):
return str(self.name)
# In order to ensure that the name is properly interpreted regardless of arguments, NamedScenario must be added to the
# tuple of ddt trivial types for which it always gets the name. See ddt.trivial_types for more information.
ddt.trivial_types = ddt.trivial_types + (NamedDataList, NamedDataDict, )
def named_data(*named_values):
"""
This decorator is to allow for meaningful names to be given to tests that would otherwise use @ddt.data and
@ddt.unpack.
Example of original ddt usage:
@ddt.ddt
class TestExample(TemplateTest):
@ddt.data(
[0, 1],
[10, 11]
)
@ddt.unpack
def test_values(self, value1, value2):
...
Example of new usage:
@ddt.ddt
class TestExample(TemplateTest):
@named_data(
['A', 0, 1],
['B', 10, 11],
)
def test_values(self, value1, value2):
...
Note that @unpack is not required.
Args:
named_values(list[Any] | dict[Any,Any]): Each named_value should be a list with the name as the first
argument, or a dictionary with 'name' as one of the keys. The name will be coerced to a string and all other
values will be passed unchanged to the test.
"""
type_of_first = None
values = []
for named_value in named_values:
if type_of_first is None:
type_of_first = type(named_value)
if not isinstance(named_value, type_of_first):
raise TypeError('@named_data expects all values to be of the same type.')
if isinstance(named_value, list):
value = NamedDataList(named_value[0], *named_value[1:])
type_of_first = type_of_first or list
elif isinstance(named_value, dict):
if 'name' not in named_value.keys():
raise ValueError('@named_data expects a dictionary with a "name" key.')
value = NamedDataDict(**named_value)
type_of_first = type_of_first or dict
else:
raise TypeError('@named_data expects a list or dictionary.')
# Remove the __doc__ attribute so @ddt.data doesn't add the NamedData class docstrings to the test name.
value.__doc__ = None
values.append(value)
def wrapper(func):
ddt.data(*values)(ddt.unpack(func))
return func
return wrapper
@ddt.ddt
class TestNamedData(unittest.TestCase):
class NonTrivialClass(object):
pass
@named_data(
['Single', 0, 1]
)
def test_single_named_value(self, value1, value2):
self.assertGreater(value2, value1)
@named_data(
['1st', 1, 2],
['2nd', 3, 4]
)
def test_multiple_named_value_lists(self, value1, value2):
self.assertGreater(value2, value1)
@named_data(
dict(name='1st', value2=1, value1=0),
{'name': '2nd', 'value2': 1, 'value1': 0}
)
def test_multiple_named_value_dicts(self, value1, value2):
self.assertGreater(value2, value1)
@named_data(
['Passes', NonTrivialClass(), True],
['Fails', 1, False]
)
def test_list_with_nontrivial_type(self, value, passes):
if passes:
self.assertIsInstance(value, self.NonTrivialClass)
else:
self.assertNotIsInstance(value, self.NonTrivialClass)
@named_data(
{'name': 'Passes', 'value': NonTrivialClass(), 'passes': True},
{'name': 'Fails', 'value': 1, 'passes': False}
)
def test_dict_with_nontrivial_type(self, value, passes):
if passes:
self.assertIsInstance(value, self.NonTrivialClass)
else:
self.assertNotIsInstance(value, self.NonTrivialClass)