Skip to content

Commit

Permalink
Ensure tests get different names by adding an ordinal
Browse files Browse the repository at this point in the history
  • Loading branch information
txels committed Mar 21, 2014
1 parent 1b25f2a commit 5c00b6e
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 34 deletions.
40 changes: 21 additions & 19 deletions ddt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import re
from functools import wraps

__version__ = '0.7.1'
__version__ = '0.8.0'

# These attributes will not conflict with any real python attribute
# They are added to the decorated test method and processed later
Expand Down Expand Up @@ -62,18 +62,21 @@ def wrapper(func):
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:
test_name = "{0}_{1}".format(name, value)
value = str(value)
except UnicodeEncodeError:
# fallback for python2
test_name = "{0}_{1}".format(
name, value.encode('ascii', 'backslashreplace')
)
value = value.encode('ascii', 'backslashreplace')
test_name = "{0}_{1}_{2}".format(name, index + 1, value)
return re.sub('\W|^(?=\d)', '_', test_name)


Expand All @@ -87,21 +90,20 @@ def ddt(cls):
For each method decorated with ``@data``, this will effectively create as
many methods as data items are passed as parameters to ``@data``.
The names of the test methods follow the pattern ``test_func_name
+ "_" + str(data)``. If ``data.__name__`` exists, it is used
instead for the test method name.
The names of the test methods follow the pattern
``original_test_name_{ordinal}_{data}``. ``ordinal`` is the position of the
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
decorator will try to load the test_data.json file located relative
to the python file containing the method that is decorated. It will,
for each ``test_name`` key create as many methods in the list of values
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):
"""
This internal method decorator feeds the test data item to the test.
Expand Down Expand Up @@ -139,19 +141,19 @@ def _raise_ve(*args):
add_test(test_name, _raise_ve, None)
else:
data = json.loads(open(data_file_path).read())
for elem in data:
for i, elem in enumerate(data):
if isinstance(data, dict):
key, value = elem, data[elem]
test_name = mk_test_name(name, key)
test_name = mk_test_name(name, key, i)
elif isinstance(data, list):
value = elem
test_name = mk_test_name(name, value)
test_name = mk_test_name(name, value, i)
add_test(test_name, func, value)

for name, func in list(cls.__dict__.items()):
if hasattr(func, DATA_ATTR):
for v in getattr(func, DATA_ATTR):
test_name = mk_test_name(name, getattr(v, "__name__", v))
for i, v in enumerate(getattr(func, DATA_ATTR)):
test_name = mk_test_name(name, getattr(v, "__name__", v), i)
if hasattr(func, UNPACK_ATTR):
if isinstance(v, tuple) or isinstance(v, list):
add_test(test_name, func, *v)
Expand Down
4 changes: 4 additions & 0 deletions docs/example.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
multiplied.


DDT will try to give the new test cases meaningful names by converting the
data values to valid python identifiers.
27 changes: 14 additions & 13 deletions test/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = json.loads(open(test_data_path).read())
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)
Expand Down Expand Up @@ -201,8 +202,8 @@ class mytest(object):
setattr(mytest, 'test_hello', data_hello)

ddt_mytest = ddt(mytest)
assert_is_not_none(getattr(ddt_mytest, 'test_hello_data1'))
assert_is_not_none(getattr(ddt_mytest, 'test_hello_2'))
assert_is_not_none(getattr(ddt_mytest, 'test_hello_1_data1'))
assert_is_not_none(getattr(ddt_mytest, 'test_hello_2_2'))


def test_ddt_data_unicode():
Expand All @@ -223,10 +224,9 @@ class mytest(object):
def test_hello(self, val):
pass

assert_is_not_none(getattr(mytest, 'test_hello_ascii'))
assert_is_not_none(getattr(mytest, 'test_hello_non_ascii__u2603'))
assert_is_not_none(
getattr(mytest, """test_hello__u__u2603____data__"""))
assert_is_not_none(getattr(mytest, 'test_hello_1_ascii'))
assert_is_not_none(getattr(mytest, 'test_hello_2_non_ascii__u2603'))
assert_is_not_none(getattr(mytest, 'test_hello_3__u__u2603____data__'))

elif six.PY3:

Expand All @@ -236,10 +236,9 @@ class mytest(object):
def test_hello(self, val):
pass

assert_is_not_none(getattr(mytest, 'test_hello_ascii'))
assert_is_not_none(getattr(mytest, 'test_hello_non_ascii__'))
assert_is_not_none(
getattr(mytest, """test_hello________data__"""))
assert_is_not_none(getattr(mytest, 'test_hello_1_ascii'))
assert_is_not_none(getattr(mytest, 'test_hello_2_non_ascii__'))
assert_is_not_none(getattr(mytest, 'test_hello_3________data__'))


def test_feed_data_with_invalid_identifier():
Expand All @@ -251,6 +250,8 @@ def test_feed_data_with_invalid_identifier():

obj = DummyInvalidIdentifier()
method = getattr(obj, tests[0])
assert_equal(method.__name__,
'test_data_with_invalid_identifier_32v2_g__Gmw845h_W_b53wi_')
assert_equal(
method.__name__,
'test_data_with_invalid_identifier_1_32v2_g__Gmw845h_W_b53wi_'
)
assert_equal(method(), '32v2 g #Gmw845h$W b53wi.')
5 changes: 3 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ deps =
flake8
six
commands =
nosetests --with-coverage --cover-package=ddt --cover-html
nosetests -s --with-coverage --cover-package=ddt --cover-html
flake8 ddt.py test

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

1 comment on commit 5c00b6e

@elyezer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

Please sign in to comment.