From 5c00b6e8444dd4d9def0cd39b5ac148e1a09b061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carles=20Barrob=C3=A9s?= Date: Fri, 21 Mar 2014 21:35:52 +0100 Subject: [PATCH] Ensure tests get different names by adding an ordinal --- ddt.py | 40 +++++++++++++++++++++------------------- docs/example.rst | 4 ++++ test/test_functional.py | 27 ++++++++++++++------------- tox.ini | 5 +++-- 4 files changed, 42 insertions(+), 34 deletions(-) diff --git a/ddt.py b/ddt.py index ee0096a..b8af6fa 100644 --- a/ddt.py +++ b/ddt.py @@ -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 @@ -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) @@ -87,9 +90,12 @@ 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 @@ -97,11 +103,7 @@ def ddt(cls): 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. @@ -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) diff --git a/docs/example.rst b/docs/example.rst index f8b503d..08bee3c 100644 --- a/docs/example.rst +++ b/docs/example.rst @@ -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. diff --git a/test/test_functional.py b/test/test_functional.py index 7519fa4..d6ff53f 100644 --- a/test/test_functional.py +++ b/test/test_functional.py @@ -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) @@ -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(): @@ -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: @@ -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(): @@ -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.') diff --git a/tox.ini b/tox.ini index a3ed9ab..6646ded 100644 --- a/tox.ini +++ b/tox.ini @@ -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