Ensure tests get different names by adding an ordinal

This commit is contained in:
Carles Barrobés
2014-03-21 21:35:52 +01:00
parent 1b25f2a3bf
commit 5c00b6e844
4 changed files with 42 additions and 34 deletions

40
ddt.py
View File

@@ -4,7 +4,7 @@ import os
import re import re
from functools import wraps from functools import wraps
__version__ = '0.7.1' __version__ = '0.8.0'
# These attributes will not conflict with any real python attribute # These attributes will not conflict with any real python attribute
# They are added to the decorated test method and processed later # They are added to the decorated test method and processed later
@@ -62,18 +62,21 @@ def file_data(value):
return wrapper return wrapper
def mk_test_name(name, value): def mk_test_name(name, value, index=0):
""" """
Generate a new name for the test named ``name``, appending ``value``. Generate a new name for a test case.
It will take the original test name and append an ordinal index and a
string representation of the value, and convert the result into a valid
python identifier by replacing extraneous characters with ``_``.
""" """
try: try:
test_name = "{0}_{1}".format(name, value) value = str(value)
except UnicodeEncodeError: except UnicodeEncodeError:
# fallback for python2 # fallback for python2
test_name = "{0}_{1}".format( value = value.encode('ascii', 'backslashreplace')
name, value.encode('ascii', 'backslashreplace') test_name = "{0}_{1}_{2}".format(name, index + 1, value)
)
return re.sub('\W|^(?=\d)', '_', test_name) return re.sub('\W|^(?=\d)', '_', test_name)
@@ -87,9 +90,12 @@ def ddt(cls):
For each method decorated with ``@data``, this will effectively create as For each method decorated with ``@data``, this will effectively create as
many methods as data items are passed as parameters to ``@data``. many methods as data items are passed as parameters to ``@data``.
The names of the test methods follow the pattern ``test_func_name The names of the test methods follow the pattern
+ "_" + str(data)``. If ``data.__name__`` exists, it is used ``original_test_name_{ordinal}_{data}``. ``ordinal`` is the position of the
instead for the test method name. data argument, starting with 1.
For data we use a string representation of the data value converted into a
valid python identifier. If ``data.__name__`` exists, we use that instead.
For each method decorated with ``@file_data('test_data.json')``, the For each method decorated with ``@file_data('test_data.json')``, the
decorator will try to load the test_data.json file located relative decorator will try to load the test_data.json file located relative
@@ -97,11 +103,7 @@ def ddt(cls):
for each ``test_name`` key create as many methods in the list of values for each ``test_name`` key create as many methods in the list of values
from the ``data`` key. from the ``data`` key.
The names of these test methods follow the pattern of
``test_name`` + str(data)``
""" """
def feed_data(func, new_name, *args, **kwargs): def feed_data(func, new_name, *args, **kwargs):
""" """
This internal method decorator feeds the test data item to the test. This internal method decorator feeds the test data item to the test.
@@ -139,19 +141,19 @@ def ddt(cls):
add_test(test_name, _raise_ve, None) add_test(test_name, _raise_ve, None)
else: else:
data = json.loads(open(data_file_path).read()) data = json.loads(open(data_file_path).read())
for elem in data: for i, elem in enumerate(data):
if isinstance(data, dict): if isinstance(data, dict):
key, value = elem, data[elem] key, value = elem, data[elem]
test_name = mk_test_name(name, key) test_name = mk_test_name(name, key, i)
elif isinstance(data, list): elif isinstance(data, list):
value = elem value = elem
test_name = mk_test_name(name, value) test_name = mk_test_name(name, value, i)
add_test(test_name, func, value) add_test(test_name, func, value)
for name, func in list(cls.__dict__.items()): for name, func in list(cls.__dict__.items()):
if hasattr(func, DATA_ATTR): if hasattr(func, DATA_ATTR):
for v in getattr(func, DATA_ATTR): for i, v in enumerate(getattr(func, DATA_ATTR)):
test_name = mk_test_name(name, getattr(v, "__name__", v)) test_name = mk_test_name(name, getattr(v, "__name__", v), i)
if hasattr(func, UNPACK_ATTR): if hasattr(func, UNPACK_ATTR):
if isinstance(v, tuple) or isinstance(v, list): if isinstance(v, tuple) or isinstance(v, list):
add_test(test_name, func, *v) add_test(test_name, func, *v)

View File

@@ -38,3 +38,7 @@ And then run them with your favourite test runner, e.g. if you use nose::
The number of test cases actually run and reported separately has been The number of test cases actually run and reported separately has been
multiplied. multiplied.
DDT will try to give the new test cases meaningful names by converting the
data values to valid python identifiers.

View File

@@ -130,7 +130,8 @@ def test_file_data_test_names_dict():
test_data_path = os.path.join(tests_dir, 'test_data_dict.json') test_data_path = os.path.join(tests_dir, 'test_data_dict.json')
test_data = json.loads(open(test_data_path).read()) test_data = json.loads(open(test_data_path).read())
created_tests = set([ created_tests = set([
"test_something_again_{0}".format(name) for name in test_data.keys() "test_something_again_{0}_{1}".format(index + 1, name)
for index, name in enumerate(test_data.keys())
]) ])
assert_equal(tests, created_tests) assert_equal(tests, created_tests)
@@ -201,8 +202,8 @@ def test_ddt_data_name_attribute():
setattr(mytest, 'test_hello', data_hello) setattr(mytest, 'test_hello', data_hello)
ddt_mytest = ddt(mytest) ddt_mytest = ddt(mytest)
assert_is_not_none(getattr(ddt_mytest, 'test_hello_data1')) assert_is_not_none(getattr(ddt_mytest, 'test_hello_1_data1'))
assert_is_not_none(getattr(ddt_mytest, 'test_hello_2')) assert_is_not_none(getattr(ddt_mytest, 'test_hello_2_2'))
def test_ddt_data_unicode(): def test_ddt_data_unicode():
@@ -223,10 +224,9 @@ def test_ddt_data_unicode():
def test_hello(self, val): def test_hello(self, val):
pass pass
assert_is_not_none(getattr(mytest, 'test_hello_ascii')) assert_is_not_none(getattr(mytest, 'test_hello_1_ascii'))
assert_is_not_none(getattr(mytest, 'test_hello_non_ascii__u2603')) assert_is_not_none(getattr(mytest, 'test_hello_2_non_ascii__u2603'))
assert_is_not_none( assert_is_not_none(getattr(mytest, 'test_hello_3__u__u2603____data__'))
getattr(mytest, """test_hello__u__u2603____data__"""))
elif six.PY3: elif six.PY3:
@@ -236,10 +236,9 @@ def test_ddt_data_unicode():
def test_hello(self, val): def test_hello(self, val):
pass pass
assert_is_not_none(getattr(mytest, 'test_hello_ascii')) assert_is_not_none(getattr(mytest, 'test_hello_1_ascii'))
assert_is_not_none(getattr(mytest, 'test_hello_non_ascii__')) assert_is_not_none(getattr(mytest, 'test_hello_2_non_ascii__'))
assert_is_not_none( assert_is_not_none(getattr(mytest, 'test_hello_3________data__'))
getattr(mytest, """test_hello________data__"""))
def test_feed_data_with_invalid_identifier(): def test_feed_data_with_invalid_identifier():
@@ -251,6 +250,8 @@ def test_feed_data_with_invalid_identifier():
obj = DummyInvalidIdentifier() obj = DummyInvalidIdentifier()
method = getattr(obj, tests[0]) method = getattr(obj, tests[0])
assert_equal(method.__name__, assert_equal(
'test_data_with_invalid_identifier_32v2_g__Gmw845h_W_b53wi_') method.__name__,
'test_data_with_invalid_identifier_1_32v2_g__Gmw845h_W_b53wi_'
)
assert_equal(method(), '32v2 g #Gmw845h$W b53wi.') assert_equal(method(), '32v2 g #Gmw845h$W b53wi.')

View File

@@ -9,14 +9,15 @@ deps =
flake8 flake8
six six
commands = commands =
nosetests --with-coverage --cover-package=ddt --cover-html nosetests -s --with-coverage --cover-package=ddt --cover-html
flake8 ddt.py test flake8 ddt.py test
[testenv:py27] [testenv:py27]
deps = deps =
{[testenv]deps} {[testenv]deps}
Sphinx Sphinx
sphinxcontrib-programoutput
commands = commands =
nosetests --with-coverage --cover-package=ddt --cover-html nosetests -s --with-coverage --cover-package=ddt --cover-html
flake8 ddt.py test flake8 ddt.py test
sphinx-build -b html docs docs/_build sphinx-build -b html docs docs/_build