Ensure tests get different names by adding an ordinal
This commit is contained in:
40
ddt.py
40
ddt.py
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.')
|
||||||
|
|||||||
5
tox.ini
5
tox.ini
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user