|
33 | 33 | INDEX_LEN = '%index_len' # store the index length of the data
|
34 | 34 |
|
35 | 35 |
|
| 36 | +# These are helper classes for @named_data that allow ddt tests to have meaningful names. |
| 37 | +class NamedDataList(list): |
| 38 | + def __init__(self, name, *args): |
| 39 | + super(NamedDataList, self).__init__(args) |
| 40 | + self.name = name |
| 41 | + |
| 42 | + def __str__(self): |
| 43 | + return str(self.name) |
| 44 | + |
| 45 | + |
| 46 | +class NamedDataDict(dict): |
| 47 | + def __init__(self, name, **kwargs): |
| 48 | + super(NamedDataDict, self).__init__(kwargs) |
| 49 | + self.name = name |
| 50 | + |
| 51 | + def __str__(self): |
| 52 | + return str(self.name) |
| 53 | + |
| 54 | + |
| 55 | +trivial_types = (type(None), bool, int, float, NamedDataList, NamedDataDict) |
36 | 56 | try:
|
37 |
| - trivial_types = (type(None), bool, int, float, basestring) |
| 57 | + trivial_types += (basestring, ) |
38 | 58 | except NameError:
|
39 |
| - trivial_types = (type(None), bool, int, float, str) |
| 59 | + trivial_types += (str, ) |
40 | 60 |
|
41 | 61 |
|
42 | 62 | @unique
|
@@ -382,3 +402,68 @@ def wrapper(cls):
|
382 | 402 | # ``arg`` is the unittest's test class when decorating with ``@ddt`` while
|
383 | 403 | # it is ``None`` when decorating a test class with ``@ddt(k=v)``.
|
384 | 404 | return wrapper(arg) if inspect.isclass(arg) else wrapper
|
| 405 | + |
| 406 | + |
| 407 | +def named_data(*named_values): |
| 408 | + """ |
| 409 | + This decorator is to allow for meaningful names to be given to tests that would otherwise use @ddt.data and |
| 410 | + @ddt.unpack. |
| 411 | +
|
| 412 | + Example of original ddt usage: |
| 413 | + @ddt.ddt |
| 414 | + class TestExample(TemplateTest): |
| 415 | + @ddt.data( |
| 416 | + [0, 1], |
| 417 | + [10, 11] |
| 418 | + ) |
| 419 | + @ddt.unpack |
| 420 | + def test_values(self, value1, value2): |
| 421 | + ... |
| 422 | +
|
| 423 | + Example of new usage: |
| 424 | + @ddt.ddt |
| 425 | + class TestExample(TemplateTest): |
| 426 | + @named_data( |
| 427 | + ['A', 0, 1], |
| 428 | + ['B', 10, 11], |
| 429 | + ) |
| 430 | + def test_values(self, value1, value2): |
| 431 | + ... |
| 432 | +
|
| 433 | + Note that @unpack is not used. |
| 434 | +
|
| 435 | + :param list[Any] | dict[Any,Any] named_values: Each named_value should be a list with the name as the first |
| 436 | + argument, or a dictionary with 'name' as one of the keys. The name will be coerced to a string and all other |
| 437 | + values will be passed unchanged to the test. |
| 438 | + """ |
| 439 | + type_of_first = None |
| 440 | + values = [] |
| 441 | + for named_value in named_values: |
| 442 | + if type_of_first is None: |
| 443 | + type_of_first = type(named_value) |
| 444 | + |
| 445 | + if not isinstance(named_value, type_of_first): |
| 446 | + raise TypeError("@named_data expects all values to be of the same type.") |
| 447 | + |
| 448 | + if isinstance(named_value, list): |
| 449 | + value = NamedDataList(named_value[0], *named_value[1:]) |
| 450 | + type_of_first = type_of_first or list |
| 451 | + |
| 452 | + elif isinstance(named_value, dict): |
| 453 | + if "name" not in named_value.keys(): |
| 454 | + raise ValueError("@named_data expects a dictionary with a 'name' key.") |
| 455 | + value = NamedDataDict(**named_value) |
| 456 | + type_of_first = type_of_first or dict |
| 457 | + else: |
| 458 | + raise TypeError("@named_data expects a list or dictionary.") |
| 459 | + |
| 460 | + # Remove the __doc__ attribute so @ddt.data doesn't add the NamedData class docstrings to the test name. |
| 461 | + value.__doc__ = None |
| 462 | + |
| 463 | + values.append(value) |
| 464 | + |
| 465 | + def wrapper(func): |
| 466 | + data(*values)(unpack(func)) |
| 467 | + return func |
| 468 | + |
| 469 | + return wrapper |
0 commit comments