parameterized-0.8.1/0000755000076600001200000000000013776411510015152 5ustar woleveradmin00000000000000parameterized-0.8.1/PKG-INFO0000644000076600001200000005645113776411510016262 0ustar woleveradmin00000000000000Metadata-Version: 2.1 Name: parameterized Version: 0.8.1 Summary: Parameterized testing with any Python test framework Home-page: https://github.com/wolever/parameterized Author: David Wolever Author-email: david@wolever.net License: FreeBSD Description: Parameterized testing with any Python test framework ==================================================== .. image:: https://img.shields.io/pypi/v/parameterized.svg :alt: PyPI :target: https://pypi.org/project/parameterized/ .. image:: https://circleci.com/gh/wolever/parameterized.svg?style=svg :alt: Circle CI :target: https://circleci.com/gh/wolever/parameterized Parameterized testing in Python sucks. ``parameterized`` fixes that. For everything. Parameterized testing for nose, parameterized testing for py.test, parameterized testing for unittest. .. code:: python # test_math.py from nose.tools import assert_equal from parameterized import parameterized, parameterized_class import unittest import math @parameterized([ (2, 2, 4), (2, 3, 8), (1, 9, 1), (0, 9, 0), ]) def test_pow(base, exponent, expected): assert_equal(math.pow(base, exponent), expected) class TestMathUnitTest(unittest.TestCase): @parameterized.expand([ ("negative", -1.5, -2.0), ("integer", 1, 1.0), ("large fraction", 1.6, 1), ]) def test_floor(self, name, input, expected): assert_equal(math.floor(input), expected) @parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [ (1, 2, 3, 2), (5, 5, 10, 25), ]) class TestMathClass(unittest.TestCase): def test_add(self): assert_equal(self.a + self.b, self.expected_sum) def test_multiply(self): assert_equal(self.a * self.b, self.expected_product) @parameterized_class([ { "a": 3, "expected": 2 }, { "b": 5, "expected": -4 }, ]) class TestMathClassDict(unittest.TestCase): a = 1 b = 1 def test_subtract(self): assert_equal(self.a - self.b, self.expected) With nose (and nose2):: $ nosetests -v test_math.py test_floor_0_negative (test_math.TestMathUnitTest) ... ok test_floor_1_integer (test_math.TestMathUnitTest) ... ok test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok test_math.test_pow(2, 2, 4, {}) ... ok test_math.test_pow(2, 3, 8, {}) ... ok test_math.test_pow(1, 9, 1, {}) ... ok test_math.test_pow(0, 9, 0, {}) ... ok test_add (test_math.TestMathClass_0) ... ok test_multiply (test_math.TestMathClass_0) ... ok test_add (test_math.TestMathClass_1) ... ok test_multiply (test_math.TestMathClass_1) ... ok test_subtract (test_math.TestMathClassDict_0) ... ok ---------------------------------------------------------------------- Ran 12 tests in 0.015s OK As the package name suggests, nose is best supported and will be used for all further examples. With py.test (version 2.0 and above):: $ py.test -v test_math.py ============================= test session starts ============================== platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0 collecting ... collected 13 items test_math.py::test_pow::[0] PASSED test_math.py::test_pow::[1] PASSED test_math.py::test_pow::[2] PASSED test_math.py::test_pow::[3] PASSED test_math.py::TestMathUnitTest::test_floor_0_negative PASSED test_math.py::TestMathUnitTest::test_floor_1_integer PASSED test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED test_math.py::TestMathClass_0::test_add PASSED test_math.py::TestMathClass_0::test_multiply PASSED test_math.py::TestMathClass_1::test_add PASSED test_math.py::TestMathClass_1::test_multiply PASSED test_math.py::TestMathClassDict_0::test_subtract PASSED ==================== 12 passed, 4 warnings in 0.16 seconds ===================== With unittest (and unittest2):: $ python -m unittest -v test_math test_floor_0_negative (test_math.TestMathUnitTest) ... ok test_floor_1_integer (test_math.TestMathUnitTest) ... ok test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok test_add (test_math.TestMathClass_0) ... ok test_multiply (test_math.TestMathClass_0) ... ok test_add (test_math.TestMathClass_1) ... ok test_multiply (test_math.TestMathClass_1) ... ok test_subtract (test_math.TestMathClassDict_0) ... ok ---------------------------------------------------------------------- Ran 8 tests in 0.001s OK (note: because unittest does not support test decorators, only tests created with ``@parameterized.expand`` will be executed) With green:: $ green test_math.py -vvv test_math TestMathClass_1 . test_method_a . test_method_b TestMathClass_2 . test_method_a . test_method_b TestMathClass_3 . test_method_a . test_method_b TestMathUnitTest . test_floor_0_negative . test_floor_1_integer . test_floor_2_large_fraction TestMathClass_0 . test_add . test_multiply TestMathClass_1 . test_add . test_multiply TestMathClassDict_0 . test_subtract Ran 12 tests in 0.121s OK (passes=9) Installation ------------ :: $ pip install parameterized Compatibility ------------- `Yes`__ (mostly). __ https://travis-ci.org/wolever/parameterized .. list-table:: :header-rows: 1 :stub-columns: 1 * - - Py2.6 - Py2.7 - Py3.4 - Py3.5 - Py3.6 - Py3.7 - Py3.8 - Py3.9 - PyPy - ``@mock.patch`` * - nose - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - nose2 - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - py.test 2 - yes - yes - no* - no* - no* - no* - yes - yes - yes - yes * - py.test 3 - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - py.test 4 - no** - no** - no** - no** - no** - no** - no** - no** - no** - no** * - py.test fixtures - no† - no† - no† - no† - no† - no† - no† - no† - no† - no† * - | unittest | (``@parameterized.expand``) - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - | unittest2 | (``@parameterized.expand``) - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes \*: py.test 2 does `does not appear to work (#71)`__ under Python 3. Please comment on the related issues if you are affected. \*\*: py.test 4 is not yet supported (but coming!) in `issue #34`__ †: py.test fixture support is documented in `issue #81`__ __ https://github.com/wolever/parameterized/issues/71 __ https://github.com/wolever/parameterized/issues/34 __ https://github.com/wolever/parameterized/issues/81 Dependencies ------------ (this section left intentionally blank) Exhaustive Usage Examples -------------------------- The ``@parameterized`` and ``@parameterized.expand`` decorators accept a list or iterable of tuples or ``param(...)``, or a callable which returns a list or iterable: .. code:: python from parameterized import parameterized, param # A list of tuples @parameterized([ (2, 3, 5), (3, 5, 8), ]) def test_add(a, b, expected): assert_equal(a + b, expected) # A list of params @parameterized([ param("10", 10), param("10", 16, base=16), ]) def test_int(str_val, expected, base=10): assert_equal(int(str_val, base=base), expected) # An iterable of params @parameterized( param.explicit(*json.loads(line)) for line in open("testcases.jsons") ) def test_from_json_file(...): ... # A callable which returns a list of tuples def load_test_cases(): return [ ("test1", ), ("test2", ), ] @parameterized(load_test_cases) def test_from_function(name): ... .. ** Note that, when using an iterator or a generator, all the items will be loaded into memory before the start of the test run (we do this explicitly to ensure that generators are exhausted exactly once in multi-process or multi-threaded testing environments). The ``@parameterized`` decorator can be used test class methods, and standalone functions: .. code:: python from parameterized import parameterized class AddTest(object): @parameterized([ (2, 3, 5), ]) def test_add(self, a, b, expected): assert_equal(a + b, expected) @parameterized([ (2, 3, 5), ]) def test_add(a, b, expected): assert_equal(a + b, expected) And ``@parameterized.expand`` can be used to generate test methods in situations where test generators cannot be used (for example, when the test class is a subclass of ``unittest.TestCase``): .. code:: python import unittest from parameterized import parameterized class AddTestCase(unittest.TestCase): @parameterized.expand([ ("2 and 3", 2, 3, 5), ("3 and 5", 2, 3, 5), ]) def test_add(self, _, a, b, expected): assert_equal(a + b, expected) Will create the test cases:: $ nosetests example.py test_add_0_2_and_3 (example.AddTestCase) ... ok test_add_1_3_and_5 (example.AddTestCase) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK Note that ``@parameterized.expand`` works by creating new methods on the test class. If the first parameter is a string, that string will be added to the end of the method name. For example, the test case above will generate the methods ``test_add_0_2_and_3`` and ``test_add_1_3_and_5``. The names of the test cases generated by ``@parameterized.expand`` can be customized using the ``name_func`` keyword argument. The value should be a function which accepts three arguments: ``testcase_func``, ``param_num``, and ``params``, and it should return the name of the test case. ``testcase_func`` will be the function to be tested, ``param_num`` will be the index of the test case parameters in the list of parameters, and ``param`` (an instance of ``param``) will be the parameters which will be used. .. code:: python import unittest from parameterized import parameterized def custom_name_func(testcase_func, param_num, param): return "%s_%s" %( testcase_func.__name__, parameterized.to_safe_name("_".join(str(x) for x in param.args)), ) class AddTestCase(unittest.TestCase): @parameterized.expand([ (2, 3, 5), (2, 3, 5), ], name_func=custom_name_func) def test_add(self, a, b, expected): assert_equal(a + b, expected) Will create the test cases:: $ nosetests example.py test_add_1_2_3 (example.AddTestCase) ... ok test_add_2_3_5 (example.AddTestCase) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK The ``param(...)`` helper class stores the parameters for one specific test case. It can be used to pass keyword arguments to test cases: .. code:: python from parameterized import parameterized, param @parameterized([ param("10", 10), param("10", 16, base=16), ]) def test_int(str_val, expected, base=10): assert_equal(int(str_val, base=base), expected) If test cases have a docstring, the parameters for that test case will be appended to the first line of the docstring. This behavior can be controlled with the ``doc_func`` argument: .. code:: python from parameterized import parameterized @parameterized([ (1, 2, 3), (4, 5, 9), ]) def test_add(a, b, expected): """ Test addition. """ assert_equal(a + b, expected) def my_doc_func(func, num, param): return "%s: %s with %s" %(num, func.__name__, param) @parameterized([ (5, 4, 1), (9, 6, 3), ], doc_func=my_doc_func) def test_subtraction(a, b, expected): assert_equal(a - b, expected) :: $ nosetests example.py Test addition. [with a=1, b=2, expected=3] ... ok Test addition. [with a=4, b=5, expected=9] ... ok 0: test_subtraction with param(*(5, 4, 1)) ... ok 1: test_subtraction with param(*(9, 6, 3)) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK Finally ``@parameterized_class`` parameterizes an entire class, using either a list of attributes, or a list of dicts that will be applied to the class: .. code:: python from yourapp.models import User from parameterized import parameterized_class @parameterized_class([ { "username": "user_1", "access_level": 1 }, { "username": "user_2", "access_level": 2, "expected_status_code": 404 }, ]) class TestUserAccessLevel(TestCase): expected_status_code = 200 def setUp(self): self.client.force_login(User.objects.get(username=self.username)[0]) def test_url_a(self): response = self.client.get('/url') self.assertEqual(response.status_code, self.expected_status_code) def tearDown(self): self.client.logout() @parameterized_class(("username", "access_level", "expected_status_code"), [ ("user_1", 1, 200), ("user_2", 2, 404) ]) class TestUserAccessLevel(TestCase): def setUp(self): self.client.force_login(User.objects.get(username=self.username)[0]) def test_url_a(self): response = self.client.get("/url") self.assertEqual(response.status_code, self.expected_status_code) def tearDown(self): self.client.logout() The ``@parameterized_class`` decorator accepts a ``class_name_func`` argument, which controls the name of the parameterized classes generated by ``@parameterized_class``: .. code:: python from parameterized import parameterized, parameterized_class def get_class_name(cls, num, params_dict): # By default the generated class named includes either the "name" # parameter (if present), or the first string value. This example shows # multiple parameters being included in the generated class name: return "%s_%s_%s%s" %( cls.__name__, num, parameterized.to_safe_name(params_dict['a']), parameterized.to_safe_name(params_dict['b']), ) @parameterized_class([ { "a": "hello", "b": " world!", "expected": "hello world!" }, { "a": "say ", "b": " cheese :)", "expected": "say cheese :)" }, ], class_name_func=get_class_name) class TestConcatenation(TestCase): def test_concat(self): self.assertEqual(self.a + self.b, self.expected) :: $ nosetests -v test_math.py test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok Using with Single Parameters ............................ If a test function only accepts one parameter and the value is not iterable, then it is possible to supply a list of values without wrapping each one in a tuple: .. code:: python @parameterized([1, 2, 3]) def test_greater_than_zero(value): assert value > 0 Note, however, that if the single parameter *is* iterable (such as a list or tuple), then it *must* be wrapped in a tuple, list, or the ``param(...)`` helper: .. code:: python @parameterized([ ([1, 2, 3], ), ([3, 3], ), ([6], ), ]) def test_sums_to_6(numbers): assert sum(numbers) == 6 (note, also, that Python requires single element tuples to be defined with a trailing comma: ``(foo, )``) Using with ``@mock.patch`` .......................... ``parameterized`` can be used with ``mock.patch``, but the argument ordering can be confusing. The ``@mock.patch(...)`` decorator must come *below* the ``@parameterized(...)``, and the mocked parameters must come *last*: .. code:: python @mock.patch("os.getpid") class TestOS(object): @parameterized(...) @mock.patch("os.fdopen") @mock.patch("os.umask") def test_method(self, param1, param2, ..., mock_umask, mock_fdopen, mock_getpid): ... Note: the same holds true when using ``@parameterized.expand``. Migrating from ``nose-parameterized`` to ``parameterized`` ---------------------------------------------------------- To migrate a codebase from ``nose-parameterized`` to ``parameterized``: 1. Update your requirements file, replacing ``nose-parameterized`` with ``parameterized``. 2. Replace all references to ``nose_parameterized`` with ``parameterized``:: $ perl -pi -e 's/nose_parameterized/parameterized/g' your-codebase/ 3. You're done! FAQ --- What happened to ``nose-parameterized``? Originally only nose was supported. But now everything is supported, and it only made sense to change the name! What do you mean when you say "nose is best supported"? There are small caveates with ``py.test`` and ``unittest``: ``py.test`` does not show the parameter values (ex, it will show ``test_add[0]`` instead of ``test_add[1, 2, 3]``), and ``unittest``/``unittest2`` do not support test generators so ``@parameterized.expand`` must be used. Why not use ``@pytest.mark.parametrize``? Because spelling is difficult. Also, ``parameterized`` doesn't require you to repeat argument names, and (using ``param``) it supports optional keyword arguments. Why do I get an ``AttributeError: 'function' object has no attribute 'expand'`` with ``@parameterized.expand``? You've likely installed the ``parametrized`` (note the missing *e*) package. Use ``parameterized`` (with the *e*) instead and you'll be all set. Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: BSD License Provides-Extra: dev parameterized-0.8.1/parameterized.egg-info/0000755000076600001200000000000013776411510021500 5ustar woleveradmin00000000000000parameterized-0.8.1/parameterized.egg-info/PKG-INFO0000644000076600001200000005645113776411510022610 0ustar woleveradmin00000000000000Metadata-Version: 2.1 Name: parameterized Version: 0.8.1 Summary: Parameterized testing with any Python test framework Home-page: https://github.com/wolever/parameterized Author: David Wolever Author-email: david@wolever.net License: FreeBSD Description: Parameterized testing with any Python test framework ==================================================== .. image:: https://img.shields.io/pypi/v/parameterized.svg :alt: PyPI :target: https://pypi.org/project/parameterized/ .. image:: https://circleci.com/gh/wolever/parameterized.svg?style=svg :alt: Circle CI :target: https://circleci.com/gh/wolever/parameterized Parameterized testing in Python sucks. ``parameterized`` fixes that. For everything. Parameterized testing for nose, parameterized testing for py.test, parameterized testing for unittest. .. code:: python # test_math.py from nose.tools import assert_equal from parameterized import parameterized, parameterized_class import unittest import math @parameterized([ (2, 2, 4), (2, 3, 8), (1, 9, 1), (0, 9, 0), ]) def test_pow(base, exponent, expected): assert_equal(math.pow(base, exponent), expected) class TestMathUnitTest(unittest.TestCase): @parameterized.expand([ ("negative", -1.5, -2.0), ("integer", 1, 1.0), ("large fraction", 1.6, 1), ]) def test_floor(self, name, input, expected): assert_equal(math.floor(input), expected) @parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [ (1, 2, 3, 2), (5, 5, 10, 25), ]) class TestMathClass(unittest.TestCase): def test_add(self): assert_equal(self.a + self.b, self.expected_sum) def test_multiply(self): assert_equal(self.a * self.b, self.expected_product) @parameterized_class([ { "a": 3, "expected": 2 }, { "b": 5, "expected": -4 }, ]) class TestMathClassDict(unittest.TestCase): a = 1 b = 1 def test_subtract(self): assert_equal(self.a - self.b, self.expected) With nose (and nose2):: $ nosetests -v test_math.py test_floor_0_negative (test_math.TestMathUnitTest) ... ok test_floor_1_integer (test_math.TestMathUnitTest) ... ok test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok test_math.test_pow(2, 2, 4, {}) ... ok test_math.test_pow(2, 3, 8, {}) ... ok test_math.test_pow(1, 9, 1, {}) ... ok test_math.test_pow(0, 9, 0, {}) ... ok test_add (test_math.TestMathClass_0) ... ok test_multiply (test_math.TestMathClass_0) ... ok test_add (test_math.TestMathClass_1) ... ok test_multiply (test_math.TestMathClass_1) ... ok test_subtract (test_math.TestMathClassDict_0) ... ok ---------------------------------------------------------------------- Ran 12 tests in 0.015s OK As the package name suggests, nose is best supported and will be used for all further examples. With py.test (version 2.0 and above):: $ py.test -v test_math.py ============================= test session starts ============================== platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0 collecting ... collected 13 items test_math.py::test_pow::[0] PASSED test_math.py::test_pow::[1] PASSED test_math.py::test_pow::[2] PASSED test_math.py::test_pow::[3] PASSED test_math.py::TestMathUnitTest::test_floor_0_negative PASSED test_math.py::TestMathUnitTest::test_floor_1_integer PASSED test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED test_math.py::TestMathClass_0::test_add PASSED test_math.py::TestMathClass_0::test_multiply PASSED test_math.py::TestMathClass_1::test_add PASSED test_math.py::TestMathClass_1::test_multiply PASSED test_math.py::TestMathClassDict_0::test_subtract PASSED ==================== 12 passed, 4 warnings in 0.16 seconds ===================== With unittest (and unittest2):: $ python -m unittest -v test_math test_floor_0_negative (test_math.TestMathUnitTest) ... ok test_floor_1_integer (test_math.TestMathUnitTest) ... ok test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok test_add (test_math.TestMathClass_0) ... ok test_multiply (test_math.TestMathClass_0) ... ok test_add (test_math.TestMathClass_1) ... ok test_multiply (test_math.TestMathClass_1) ... ok test_subtract (test_math.TestMathClassDict_0) ... ok ---------------------------------------------------------------------- Ran 8 tests in 0.001s OK (note: because unittest does not support test decorators, only tests created with ``@parameterized.expand`` will be executed) With green:: $ green test_math.py -vvv test_math TestMathClass_1 . test_method_a . test_method_b TestMathClass_2 . test_method_a . test_method_b TestMathClass_3 . test_method_a . test_method_b TestMathUnitTest . test_floor_0_negative . test_floor_1_integer . test_floor_2_large_fraction TestMathClass_0 . test_add . test_multiply TestMathClass_1 . test_add . test_multiply TestMathClassDict_0 . test_subtract Ran 12 tests in 0.121s OK (passes=9) Installation ------------ :: $ pip install parameterized Compatibility ------------- `Yes`__ (mostly). __ https://travis-ci.org/wolever/parameterized .. list-table:: :header-rows: 1 :stub-columns: 1 * - - Py2.6 - Py2.7 - Py3.4 - Py3.5 - Py3.6 - Py3.7 - Py3.8 - Py3.9 - PyPy - ``@mock.patch`` * - nose - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - nose2 - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - py.test 2 - yes - yes - no* - no* - no* - no* - yes - yes - yes - yes * - py.test 3 - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - py.test 4 - no** - no** - no** - no** - no** - no** - no** - no** - no** - no** * - py.test fixtures - no† - no† - no† - no† - no† - no† - no† - no† - no† - no† * - | unittest | (``@parameterized.expand``) - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - | unittest2 | (``@parameterized.expand``) - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes \*: py.test 2 does `does not appear to work (#71)`__ under Python 3. Please comment on the related issues if you are affected. \*\*: py.test 4 is not yet supported (but coming!) in `issue #34`__ †: py.test fixture support is documented in `issue #81`__ __ https://github.com/wolever/parameterized/issues/71 __ https://github.com/wolever/parameterized/issues/34 __ https://github.com/wolever/parameterized/issues/81 Dependencies ------------ (this section left intentionally blank) Exhaustive Usage Examples -------------------------- The ``@parameterized`` and ``@parameterized.expand`` decorators accept a list or iterable of tuples or ``param(...)``, or a callable which returns a list or iterable: .. code:: python from parameterized import parameterized, param # A list of tuples @parameterized([ (2, 3, 5), (3, 5, 8), ]) def test_add(a, b, expected): assert_equal(a + b, expected) # A list of params @parameterized([ param("10", 10), param("10", 16, base=16), ]) def test_int(str_val, expected, base=10): assert_equal(int(str_val, base=base), expected) # An iterable of params @parameterized( param.explicit(*json.loads(line)) for line in open("testcases.jsons") ) def test_from_json_file(...): ... # A callable which returns a list of tuples def load_test_cases(): return [ ("test1", ), ("test2", ), ] @parameterized(load_test_cases) def test_from_function(name): ... .. ** Note that, when using an iterator or a generator, all the items will be loaded into memory before the start of the test run (we do this explicitly to ensure that generators are exhausted exactly once in multi-process or multi-threaded testing environments). The ``@parameterized`` decorator can be used test class methods, and standalone functions: .. code:: python from parameterized import parameterized class AddTest(object): @parameterized([ (2, 3, 5), ]) def test_add(self, a, b, expected): assert_equal(a + b, expected) @parameterized([ (2, 3, 5), ]) def test_add(a, b, expected): assert_equal(a + b, expected) And ``@parameterized.expand`` can be used to generate test methods in situations where test generators cannot be used (for example, when the test class is a subclass of ``unittest.TestCase``): .. code:: python import unittest from parameterized import parameterized class AddTestCase(unittest.TestCase): @parameterized.expand([ ("2 and 3", 2, 3, 5), ("3 and 5", 2, 3, 5), ]) def test_add(self, _, a, b, expected): assert_equal(a + b, expected) Will create the test cases:: $ nosetests example.py test_add_0_2_and_3 (example.AddTestCase) ... ok test_add_1_3_and_5 (example.AddTestCase) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK Note that ``@parameterized.expand`` works by creating new methods on the test class. If the first parameter is a string, that string will be added to the end of the method name. For example, the test case above will generate the methods ``test_add_0_2_and_3`` and ``test_add_1_3_and_5``. The names of the test cases generated by ``@parameterized.expand`` can be customized using the ``name_func`` keyword argument. The value should be a function which accepts three arguments: ``testcase_func``, ``param_num``, and ``params``, and it should return the name of the test case. ``testcase_func`` will be the function to be tested, ``param_num`` will be the index of the test case parameters in the list of parameters, and ``param`` (an instance of ``param``) will be the parameters which will be used. .. code:: python import unittest from parameterized import parameterized def custom_name_func(testcase_func, param_num, param): return "%s_%s" %( testcase_func.__name__, parameterized.to_safe_name("_".join(str(x) for x in param.args)), ) class AddTestCase(unittest.TestCase): @parameterized.expand([ (2, 3, 5), (2, 3, 5), ], name_func=custom_name_func) def test_add(self, a, b, expected): assert_equal(a + b, expected) Will create the test cases:: $ nosetests example.py test_add_1_2_3 (example.AddTestCase) ... ok test_add_2_3_5 (example.AddTestCase) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK The ``param(...)`` helper class stores the parameters for one specific test case. It can be used to pass keyword arguments to test cases: .. code:: python from parameterized import parameterized, param @parameterized([ param("10", 10), param("10", 16, base=16), ]) def test_int(str_val, expected, base=10): assert_equal(int(str_val, base=base), expected) If test cases have a docstring, the parameters for that test case will be appended to the first line of the docstring. This behavior can be controlled with the ``doc_func`` argument: .. code:: python from parameterized import parameterized @parameterized([ (1, 2, 3), (4, 5, 9), ]) def test_add(a, b, expected): """ Test addition. """ assert_equal(a + b, expected) def my_doc_func(func, num, param): return "%s: %s with %s" %(num, func.__name__, param) @parameterized([ (5, 4, 1), (9, 6, 3), ], doc_func=my_doc_func) def test_subtraction(a, b, expected): assert_equal(a - b, expected) :: $ nosetests example.py Test addition. [with a=1, b=2, expected=3] ... ok Test addition. [with a=4, b=5, expected=9] ... ok 0: test_subtraction with param(*(5, 4, 1)) ... ok 1: test_subtraction with param(*(9, 6, 3)) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK Finally ``@parameterized_class`` parameterizes an entire class, using either a list of attributes, or a list of dicts that will be applied to the class: .. code:: python from yourapp.models import User from parameterized import parameterized_class @parameterized_class([ { "username": "user_1", "access_level": 1 }, { "username": "user_2", "access_level": 2, "expected_status_code": 404 }, ]) class TestUserAccessLevel(TestCase): expected_status_code = 200 def setUp(self): self.client.force_login(User.objects.get(username=self.username)[0]) def test_url_a(self): response = self.client.get('/url') self.assertEqual(response.status_code, self.expected_status_code) def tearDown(self): self.client.logout() @parameterized_class(("username", "access_level", "expected_status_code"), [ ("user_1", 1, 200), ("user_2", 2, 404) ]) class TestUserAccessLevel(TestCase): def setUp(self): self.client.force_login(User.objects.get(username=self.username)[0]) def test_url_a(self): response = self.client.get("/url") self.assertEqual(response.status_code, self.expected_status_code) def tearDown(self): self.client.logout() The ``@parameterized_class`` decorator accepts a ``class_name_func`` argument, which controls the name of the parameterized classes generated by ``@parameterized_class``: .. code:: python from parameterized import parameterized, parameterized_class def get_class_name(cls, num, params_dict): # By default the generated class named includes either the "name" # parameter (if present), or the first string value. This example shows # multiple parameters being included in the generated class name: return "%s_%s_%s%s" %( cls.__name__, num, parameterized.to_safe_name(params_dict['a']), parameterized.to_safe_name(params_dict['b']), ) @parameterized_class([ { "a": "hello", "b": " world!", "expected": "hello world!" }, { "a": "say ", "b": " cheese :)", "expected": "say cheese :)" }, ], class_name_func=get_class_name) class TestConcatenation(TestCase): def test_concat(self): self.assertEqual(self.a + self.b, self.expected) :: $ nosetests -v test_math.py test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok Using with Single Parameters ............................ If a test function only accepts one parameter and the value is not iterable, then it is possible to supply a list of values without wrapping each one in a tuple: .. code:: python @parameterized([1, 2, 3]) def test_greater_than_zero(value): assert value > 0 Note, however, that if the single parameter *is* iterable (such as a list or tuple), then it *must* be wrapped in a tuple, list, or the ``param(...)`` helper: .. code:: python @parameterized([ ([1, 2, 3], ), ([3, 3], ), ([6], ), ]) def test_sums_to_6(numbers): assert sum(numbers) == 6 (note, also, that Python requires single element tuples to be defined with a trailing comma: ``(foo, )``) Using with ``@mock.patch`` .......................... ``parameterized`` can be used with ``mock.patch``, but the argument ordering can be confusing. The ``@mock.patch(...)`` decorator must come *below* the ``@parameterized(...)``, and the mocked parameters must come *last*: .. code:: python @mock.patch("os.getpid") class TestOS(object): @parameterized(...) @mock.patch("os.fdopen") @mock.patch("os.umask") def test_method(self, param1, param2, ..., mock_umask, mock_fdopen, mock_getpid): ... Note: the same holds true when using ``@parameterized.expand``. Migrating from ``nose-parameterized`` to ``parameterized`` ---------------------------------------------------------- To migrate a codebase from ``nose-parameterized`` to ``parameterized``: 1. Update your requirements file, replacing ``nose-parameterized`` with ``parameterized``. 2. Replace all references to ``nose_parameterized`` with ``parameterized``:: $ perl -pi -e 's/nose_parameterized/parameterized/g' your-codebase/ 3. You're done! FAQ --- What happened to ``nose-parameterized``? Originally only nose was supported. But now everything is supported, and it only made sense to change the name! What do you mean when you say "nose is best supported"? There are small caveates with ``py.test`` and ``unittest``: ``py.test`` does not show the parameter values (ex, it will show ``test_add[0]`` instead of ``test_add[1, 2, 3]``), and ``unittest``/``unittest2`` do not support test generators so ``@parameterized.expand`` must be used. Why not use ``@pytest.mark.parametrize``? Because spelling is difficult. Also, ``parameterized`` doesn't require you to repeat argument names, and (using ``param``) it supports optional keyword arguments. Why do I get an ``AttributeError: 'function' object has no attribute 'expand'`` with ``@parameterized.expand``? You've likely installed the ``parametrized`` (note the missing *e*) package. Use ``parameterized`` (with the *e*) instead and you'll be all set. Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: BSD License Provides-Extra: dev parameterized-0.8.1/parameterized.egg-info/SOURCES.txt0000644000076600001200000000074313776411510023370 0ustar woleveradmin00000000000000.gitignore .hgignore .travis.yml CHANGELOG.txt LICENSE.txt README.rst setup.cfg setup.py tox.ini misspelling-helper/nose-parametrized/MANIFEST misspelling-helper/nose-parametrized/setup.py parameterized/__init__.py parameterized/parameterized.py parameterized/test.py parameterized.egg-info/PKG-INFO parameterized.egg-info/SOURCES.txt parameterized.egg-info/dependency_links.txt parameterized.egg-info/pbr.json parameterized.egg-info/requires.txt parameterized.egg-info/top_level.txtparameterized-0.8.1/parameterized.egg-info/pbr.json0000644000076600001200000000005713776411510023160 0ustar woleveradmin00000000000000{"is_release": false, "git_version": "80cbc49"}parameterized-0.8.1/parameterized.egg-info/requires.txt0000644000076600001200000000001613776411510024075 0ustar woleveradmin00000000000000 [dev] jinja2 parameterized-0.8.1/parameterized.egg-info/top_level.txt0000644000076600001200000000001613776411510024227 0ustar woleveradmin00000000000000parameterized parameterized-0.8.1/parameterized.egg-info/dependency_links.txt0000644000076600001200000000000113776411510025546 0ustar woleveradmin00000000000000 parameterized-0.8.1/parameterized/0000755000076600001200000000000013776411510020006 5ustar woleveradmin00000000000000parameterized-0.8.1/parameterized/__init__.py0000644000076600001200000000013413776411313022116 0ustar woleveradmin00000000000000from .parameterized import parameterized, param, parameterized_class __version__ = "0.8.1" parameterized-0.8.1/parameterized/test.py0000644000076600001200000004435713776411255021362 0ustar woleveradmin00000000000000# coding=utf-8 import inspect import mock from unittest import TestCase from nose.tools import assert_equal, assert_raises from .parameterized import ( PY3, PY2, parameterized, param, parameterized_argument_value_pairs, short_repr, detect_runner, parameterized_class, SkipTest, ) def assert_contains(haystack, needle): if needle not in haystack: raise AssertionError("%r not in %r" %(needle, haystack)) runner = detect_runner() UNITTEST = runner.startswith("unittest") NOSE2 = (runner == "nose2") PYTEST = (runner == "pytest") SKIP_FLAGS = { "generator": UNITTEST, "standalone": UNITTEST, # nose2 doesn't run tests on old-style classes under Py2, so don't expect # these tests to run under nose2. "py2nose2": (PY2 and NOSE2), "pytest": PYTEST, } missing_tests = set() def expect(skip, tests=None): if tests is None: tests = skip skip = None if any(SKIP_FLAGS.get(f) for f in (skip or "").split()): return missing_tests.update(tests) test_params = [ (42, ), "foo0", param("foo1"), param("foo2", bar=42), ] expect("standalone", [ "test_naked_function('foo0', bar=None)", "test_naked_function('foo1', bar=None)", "test_naked_function('foo2', bar=42)", "test_naked_function(42, bar=None)", ]) @parameterized(test_params) def test_naked_function(foo, bar=None): missing_tests.remove("test_naked_function(%r, bar=%r)" %(foo, bar)) class TestParameterized(object): expect("generator", [ "test_instance_method('foo0', bar=None)", "test_instance_method('foo1', bar=None)", "test_instance_method('foo2', bar=42)", "test_instance_method(42, bar=None)", ]) @parameterized(test_params) def test_instance_method(self, foo, bar=None): missing_tests.remove("test_instance_method(%r, bar=%r)" %(foo, bar)) if not PYTEST: # py.test doesn't use xunit-style setup/teardown, so these tests don't apply class TestSetupTeardown(object): expect("generator", [ "test_setup(setup 1)", "teardown_called(teardown 1)", "test_setup(setup 2)", "teardown_called(teardown 2)", ]) stack = ["setup 1", "teardown 1", "setup 2", "teardown 2"] actual_order = "error: setup not called" def setUp(self): self.actual_order = self.stack.pop(0) def tearDown(self): missing_tests.remove("teardown_called(%s)" %(self.stack.pop(0), )) @parameterized([(1, ), (2, )]) def test_setup(self, count, *a): assert_equal(self.actual_order, "setup %s" %(count, )) missing_tests.remove("test_setup(%s)" %(self.actual_order, )) def custom_naming_func(custom_tag): def custom_naming_func(testcase_func, param_num, param): return testcase_func.__name__ + ('_%s_name_' % custom_tag) + str(param.args[0]) return custom_naming_func @mock.patch("os.getpid") class TestParameterizedExpandWithMockPatchForClass(TestCase): expect([ "test_one_function_patch_decorator('foo1', 'umask', 'getpid')", "test_one_function_patch_decorator('foo0', 'umask', 'getpid')", "test_one_function_patch_decorator(42, 'umask', 'getpid')", ]) @parameterized.expand([(42, ), "foo0", param("foo1")]) @mock.patch("os.umask") def test_one_function_patch_decorator(self, foo, mock_umask, mock_getpid): missing_tests.remove("test_one_function_patch_decorator(%r, %r, %r)" % (foo, mock_umask._mock_name, mock_getpid._mock_name)) expect([ "test_multiple_function_patch_decorator" "(42, 51, 'umask', 'fdopen', 'getpid')", "test_multiple_function_patch_decorator" "('foo0', 'bar0', 'umask', 'fdopen', 'getpid')", "test_multiple_function_patch_decorator" "('foo1', 'bar1', 'umask', 'fdopen', 'getpid')", ]) @parameterized.expand([(42, 51), ("foo0", "bar0"), param("foo1", "bar1")]) @mock.patch("os.fdopen") @mock.patch("os.umask") def test_multiple_function_patch_decorator(self, foo, bar, mock_umask, mock_fdopen, mock_getpid): missing_tests.remove("test_multiple_function_patch_decorator" "(%r, %r, %r, %r, %r)" % (foo, bar, mock_umask._mock_name, mock_fdopen._mock_name, mock_getpid._mock_name)) @mock.patch("os.getpid") class TestParameterizedExpandWithNoExpand(object): expect("generator", [ "test_patch_class_no_expand(42, 51, 'umask', 'getpid')", ]) @parameterized([(42, 51)]) @mock.patch("os.umask") def test_patch_class_no_expand(self, foo, bar, mock_umask, mock_getpid): missing_tests.remove("test_patch_class_no_expand" "(%r, %r, %r, %r)" % (foo, bar, mock_umask._mock_name, mock_getpid._mock_name)) class TestParameterizedExpandWithNoMockPatchForClass(TestCase): expect([ "test_one_function_patch_decorator('foo1', 'umask')", "test_one_function_patch_decorator('foo0', 'umask')", "test_one_function_patch_decorator(42, 'umask')", ]) @parameterized.expand([(42, ), "foo0", param("foo1")]) @mock.patch("os.umask") def test_one_function_patch_decorator(self, foo, mock_umask): missing_tests.remove("test_one_function_patch_decorator(%r, %r)" % (foo, mock_umask._mock_name)) expect([ "test_multiple_function_patch_decorator(42, 51, 'umask', 'fdopen')", "test_multiple_function_patch_decorator('foo0', 'bar0', 'umask', 'fdopen')", "test_multiple_function_patch_decorator('foo1', 'bar1', 'umask', 'fdopen')", ]) @parameterized.expand([(42, 51), ("foo0", "bar0"), param("foo1", "bar1")]) @mock.patch("os.fdopen") @mock.patch("os.umask") def test_multiple_function_patch_decorator(self, foo, bar, mock_umask, mock_fdopen): missing_tests.remove("test_multiple_function_patch_decorator" "(%r, %r, %r, %r)" % (foo, bar, mock_umask._mock_name, mock_fdopen._mock_name)) class TestParameterizedExpandWithNoMockPatchForClassNoExpand(object): expect("generator", [ "test_patch_no_expand(42, 51, 'umask')", ]) @parameterized([(42, 51)]) @mock.patch("os.umask") def test_patch_no_expand(self, foo, bar, mock_umask): missing_tests.remove("test_patch_no_expand(%r, %r, %r)" % (foo, bar, mock_umask._mock_name)) expect("standalone", [ "test_mock_patch_standalone_function(42, 'umask')", ]) @parameterized([(42, )]) @mock.patch("os.umask") def test_mock_patch_standalone_function(foo, mock_umask): missing_tests.remove( "test_mock_patch_standalone_function(%r, %r)" %( foo, mock_umask._mock_name ) ) class TestParamerizedOnTestCase(TestCase): expect([ "test_on_TestCase('foo0', bar=None)", "test_on_TestCase('foo1', bar=None)", "test_on_TestCase('foo2', bar=42)", "test_on_TestCase(42, bar=None)", ]) @parameterized.expand(test_params) def test_on_TestCase(self, foo, bar=None): missing_tests.remove("test_on_TestCase(%r, bar=%r)" %(foo, bar)) expect([ "test_on_TestCase2_custom_name_42(42, bar=None)", "test_on_TestCase2_custom_name_foo0('foo0', bar=None)", "test_on_TestCase2_custom_name_foo1('foo1', bar=None)", "test_on_TestCase2_custom_name_foo2('foo2', bar=42)", ]) @parameterized.expand(test_params, name_func=custom_naming_func("custom")) def test_on_TestCase2(self, foo, bar=None): stack = inspect.stack() frame = stack[1] frame_locals = frame[0].f_locals nose_test_method_name = frame_locals['a'][0]._testMethodName expected_name = "test_on_TestCase2_custom_name_" + str(foo) assert_equal(nose_test_method_name, expected_name, "Test Method name '%s' did not get customized to expected: '%s'" % (nose_test_method_name, expected_name)) missing_tests.remove("%s(%r, bar=%r)" %(expected_name, foo, bar)) class TestParameterizedExpandDocstring(TestCase): def _assert_docstring(self, expected_docstring, rstrip=False): """ Checks the current test method's docstring. Must be called directly from the test method. """ stack = inspect.stack() f_locals = stack[3][0].f_locals test_method = ( f_locals.get("testMethod") or # Py27 f_locals.get("function") or # Py33 f_locals.get("method") or # Py38 f_locals.get("testfunction") or # Py382 None ) if test_method is None: raise AssertionError("uh oh, unittest changed a local variable name") actual_docstring = test_method.__doc__ if rstrip: actual_docstring = actual_docstring.rstrip() assert_equal(actual_docstring, expected_docstring) @parameterized.expand([param("foo")], doc_func=lambda f, n, p: "stuff") def test_custom_doc_func(self, foo, bar=None): """Documentation""" self._assert_docstring("stuff") @parameterized.expand([param("foo")]) def test_single_line_docstring(self, foo): """Documentation.""" self._assert_docstring("Documentation [with foo=%r]." %(foo, )) @parameterized.expand([param("foo")]) def test_empty_docstring(self, foo): "" self._assert_docstring("[with foo=%r]" %(foo, )) @parameterized.expand([param("foo")]) def test_multiline_documentation(self, foo): """Documentation. More""" self._assert_docstring( "Documentation [with foo=%r].\n\n" " More" %(foo, ) ) @parameterized.expand([param("foo")]) def test_unicode_docstring(self, foo): u"""Döcumentation.""" self._assert_docstring(u"Döcumentation [with foo=%r]." %(foo, )) @parameterized.expand([param("foo", )]) def test_default_values_get_correct_value(self, foo, bar=12): """Documentation""" self._assert_docstring("Documentation [with foo=%r, bar=%r]" %(foo, bar)) @parameterized.expand([param("foo", )]) def test_with_leading_newline(self, foo, bar=12): """ Documentation """ self._assert_docstring("Documentation [with foo=%r, bar=%r]" %(foo, bar), rstrip=True) def test_warns_when_using_parameterized_with_TestCase(): try: class TestTestCaseWarnsOnBadUseOfParameterized(TestCase): @parameterized([(42, )]) def test_in_subclass_of_TestCase(self, foo): pass except Exception as e: assert_contains(str(e), "parameterized.expand") else: raise AssertionError("Expected exception not raised") def test_helpful_error_on_invalid_parameters(): try: parameterized([1432141234243])(lambda: None) except Exception as e: assert_contains(str(e), "Parameters must be tuples") else: raise AssertionError("Expected exception not raised") def test_helpful_error_on_empty_iterable_input(): try: parameterized([])(lambda: None) except ValueError as e: assert_contains(str(e), "iterable is empty") else: raise AssertionError("Expected exception not raised") def test_skip_test_on_empty_iterable(): func = parameterized([], skip_on_empty=True)(lambda: None) assert_raises(SkipTest, func) def test_helpful_error_on_empty_iterable_input_expand(): try: class ExpectErrorOnEmptyInput(TestCase): @parameterized.expand([]) def test_expect_error(self): pass except ValueError as e: assert_contains(str(e), "iterable is empty") else: raise AssertionError("Expected exception not raised") expect("stadalone generator", [ "test_wrapped_iterable_input('foo')", ]) @parameterized(lambda: iter(["foo"])) def test_wrapped_iterable_input(foo): missing_tests.remove("test_wrapped_iterable_input(%r)" %(foo, )) def test_helpful_error_on_non_iterable_input(): try: parameterized(lambda: 42)(lambda: None) except Exception as e: assert_contains(str(e), "is not iterable") else: raise AssertionError("Expected exception not raised") def tearDownModule(): missing = sorted(list(missing_tests)) assert_equal(missing, []) def test_old_style_classes(): if PY3: raise SkipTest("Py3 doesn't have old-style classes") class OldStyleClass: @parameterized(["foo"]) def parameterized_method(self, param): pass try: list(OldStyleClass().parameterized_method()) except TypeError as e: assert_contains(str(e), "new-style") assert_contains(str(e), "parameterized.expand") assert_contains(str(e), "OldStyleClass") else: raise AssertionError("expected TypeError not raised by old-style class") class TestOldStyleClass: expect("py2nose2 generator", [ "test_on_old_style_class('foo')", "test_on_old_style_class('bar')", ]) @parameterized.expand(["foo", "bar"]) def test_old_style_classes(self, param): missing_tests.remove("test_on_old_style_class(%r)" %(param, )) @parameterized([ ("", param(), []), ("*a, **kw", param(), []), ("*a, **kw", param(1, foo=42), [("*a", (1, )), ("**kw", {"foo": 42})]), ("foo", param(1), [("foo", 1)]), ("foo, *a", param(1), [("foo", 1)]), ("foo, *a", param(1, 9), [("foo", 1), ("*a", (9, ))]), ("foo, *a, **kw", param(1, bar=9), [("foo", 1), ("**kw", {"bar": 9})]), ("x=9", param(), [("x", 9)]), ("x=9", param(1), [("x", 1)]), ("x, y=9, *a, **kw", param(1), [("x", 1), ("y", 9)]), ("x, y=9, *a, **kw", param(1, 2), [("x", 1), ("y", 2)]), ("x, y=9, *a, **kw", param(1, 2, 3), [("x", 1), ("y", 2), ("*a", (3, ))]), ("x, y=9, *a, **kw", param(1, y=2), [("x", 1), ("y", 2)]), ("x, y=9, *a, **kw", param(1, z=2), [("x", 1), ("y", 9), ("**kw", {"z": 2})]), ("x, y=9, *a, **kw", param(1, 2, 3, z=3), [("x", 1), ("y", 2), ("*a", (3, )), ("**kw", {"z": 3})]), ]) def test_parameterized_argument_value_pairs(func_params, p, expected): helper = eval("lambda %s: None" %(func_params, )) actual = parameterized_argument_value_pairs(helper, p) assert_equal(actual, expected) @parameterized([ ("abcd", "'abcd'"), ("123456789", "'12...89'"), (123456789, "123...789"), (123456789, "12...89", 4), ]) def test_short_repr(input, expected, n=6): assert_equal(short_repr(input, n=n), expected) @parameterized([ ("foo", ), ]) def test_with_docstring(input): """ Docstring! """ pass cases_over_10 = [(i, i+1) for i in range(11)] @parameterized(cases_over_10) def test_cases_over_10(input, expected): assert_equal(input, expected-1) @parameterized_class(("a", "b", "c"), [ ("foo", 1, 2), (0, 1, 2), ]) class TestParameterizedClass(TestCase): expect([ "TestParameterizedClass_0_foo:test_method_a('foo', 1, 2)", "TestParameterizedClass_0_foo:test_method_b('foo', 1, 2)", "TestParameterizedClass_0_foo:testCamelCaseMethodC('foo', 1, 2)", "TestParameterizedClass_1:test_method_a(0, 1, 2)", "TestParameterizedClass_1:test_method_b(0, 1, 2)", "TestParameterizedClass_1:testCamelCaseMethodC(0, 1, 2)", ]) def _assertions(self, test_name): assert hasattr(self, "a") assert_equal(self.b + self.c, 3) missing_tests.remove("%s:%s(%r, %r, %r)" %( self.__class__.__name__, test_name, self.a, self.b, self.c, )) def test_method_a(self): self._assertions("test_method_a") def test_method_b(self): self._assertions("test_method_b") def testCamelCaseMethodC(self): self._assertions("testCamelCaseMethodC") @parameterized_class(("a", ), [ (1, ), (2, ), ], class_name_func=lambda cls, idx, attrs: "%s_custom_func_%s" %(cls.__name__, attrs["a"])) class TestNamedParameterizedClass(TestCase): expect([ "TestNamedParameterizedClass_custom_func_1:test_method(1)", "TestNamedParameterizedClass_custom_func_2:test_method(2)", ]) def test_method(self): missing_tests.remove("%s:test_method(%r)" %( self.__class__.__name__, self.a, )) @parameterized_class([ {"foo": 42}, {"bar": "some stuff"}, {"bar": "other stuff", "name": "some name", "foo": 12}, ]) class TestParameterizedClassDict(TestCase): expect([ "TestParameterizedClassDict_0:setUp(42, 'empty')", "TestParameterizedClassDict_0:test_method(42, 'empty')", "TestParameterizedClassDict_0:tearDown(42, 'empty')", "TestParameterizedClassDict_1_some_stuff:setUp(0, 'some stuff')", "TestParameterizedClassDict_1_some_stuff:test_method(0, 'some stuff')", "TestParameterizedClassDict_1_some_stuff:tearDown(0, 'some stuff')", "TestParameterizedClassDict_2_some_name:setUp(12, 'other stuff')", "TestParameterizedClassDict_2_some_name:test_method(12, 'other stuff')", "TestParameterizedClassDict_2_some_name:tearDown(12, 'other stuff')", ]) foo = 0 bar = 'empty' def setUp(self): # Ensure that super() works (issue #73) super(TestParameterizedClassDict, self).setUp() missing_tests.remove("%s:setUp(%r, %r)" %( self.__class__.__name__, self.foo, self.bar, )) def tearDown(self): # Ensure that super() works (issue #73) super(TestParameterizedClassDict, self).tearDown() missing_tests.remove("%s:tearDown(%r, %r)" %( self.__class__.__name__, self.foo, self.bar, )) def test_method(self): missing_tests.remove("%s:test_method(%r, %r)" %( self.__class__.__name__, self.foo, self.bar, )) class TestUnicodeDocstring(object): @parameterized.expand([ 'value1', 'vålüé¡' ]) def test_with_docstring(self, param): """ Это док-стринг, содержащий не-ascii символы """ pass parameterized-0.8.1/parameterized/parameterized.py0000644000076600001200000005367513776411255023242 0ustar woleveradmin00000000000000import re import sys import inspect import warnings from functools import wraps from types import MethodType as MethodType from collections import namedtuple try: from collections import OrderedDict as MaybeOrderedDict except ImportError: MaybeOrderedDict = dict from unittest import TestCase try: from unittest import SkipTest except ImportError: class SkipTest(Exception): pass PY3 = sys.version_info[0] == 3 PY2 = sys.version_info[0] == 2 if PY3: # Python 3 doesn't have an InstanceType, so just use a dummy type. class InstanceType(): pass lzip = lambda *a: list(zip(*a)) text_type = str string_types = str, bytes_type = bytes def make_method(func, instance, type): if instance is None: return func return MethodType(func, instance) else: from types import InstanceType lzip = zip text_type = unicode bytes_type = str string_types = basestring, def make_method(func, instance, type): return MethodType(func, instance, type) def to_text(x): if isinstance(x, text_type): return x try: return text_type(x, "utf-8") except UnicodeDecodeError: return text_type(x, "latin1") CompatArgSpec = namedtuple("CompatArgSpec", "args varargs keywords defaults") def getargspec(func): if PY2: return CompatArgSpec(*inspect.getargspec(func)) args = inspect.getfullargspec(func) if args.kwonlyargs: raise TypeError(( "parameterized does not (yet) support functions with keyword " "only arguments, but %r has keyword only arguments. " "Please open an issue with your usecase if this affects you: " "https://github.com/wolever/parameterized/issues/new" ) %(func, )) return CompatArgSpec(*args[:4]) def skip_on_empty_helper(*a, **kw): raise SkipTest("parameterized input is empty") def reapply_patches_if_need(func): def dummy_wrapper(orgfunc): @wraps(orgfunc) def dummy_func(*args, **kwargs): return orgfunc(*args, **kwargs) return dummy_func if hasattr(func, 'patchings'): func = dummy_wrapper(func) tmp_patchings = func.patchings delattr(func, 'patchings') for patch_obj in tmp_patchings: func = patch_obj.decorate_callable(func) return func def delete_patches_if_need(func): if hasattr(func, 'patchings'): func.patchings[:] = [] _param = namedtuple("param", "args kwargs") class param(_param): """ Represents a single parameter to a test case. For example:: >>> p = param("foo", bar=16) >>> p param("foo", bar=16) >>> p.args ('foo', ) >>> p.kwargs {'bar': 16} Intended to be used as an argument to ``@parameterized``:: @parameterized([ param("foo", bar=16), ]) def test_stuff(foo, bar=16): pass """ def __new__(cls, *args , **kwargs): return _param.__new__(cls, args, kwargs) @classmethod def explicit(cls, args=None, kwargs=None): """ Creates a ``param`` by explicitly specifying ``args`` and ``kwargs``:: >>> param.explicit([1,2,3]) param(*(1, 2, 3)) >>> param.explicit(kwargs={"foo": 42}) param(*(), **{"foo": "42"}) """ args = args or () kwargs = kwargs or {} return cls(*args, **kwargs) @classmethod def from_decorator(cls, args): """ Returns an instance of ``param()`` for ``@parameterized`` argument ``args``:: >>> param.from_decorator((42, )) param(args=(42, ), kwargs={}) >>> param.from_decorator("foo") param(args=("foo", ), kwargs={}) """ if isinstance(args, param): return args elif isinstance(args, string_types): args = (args, ) try: return cls(*args) except TypeError as e: if "after * must be" not in str(e): raise raise TypeError( "Parameters must be tuples, but %r is not (hint: use '(%r, )')" %(args, args), ) def __repr__(self): return "param(*%r, **%r)" %self class QuietOrderedDict(MaybeOrderedDict): """ When OrderedDict is available, use it to make sure that the kwargs in doc strings are consistently ordered. """ __str__ = dict.__str__ __repr__ = dict.__repr__ def parameterized_argument_value_pairs(func, p): """Return tuples of parameterized arguments and their values. This is useful if you are writing your own doc_func function and need to know the values for each parameter name:: >>> def func(a, foo=None, bar=42, **kwargs): pass >>> p = param(1, foo=7, extra=99) >>> parameterized_argument_value_pairs(func, p) [("a", 1), ("foo", 7), ("bar", 42), ("**kwargs", {"extra": 99})] If the function's first argument is named ``self`` then it will be ignored:: >>> def func(self, a): pass >>> p = param(1) >>> parameterized_argument_value_pairs(func, p) [("a", 1)] Additionally, empty ``*args`` or ``**kwargs`` will be ignored:: >>> def func(foo, *args): pass >>> p = param(1) >>> parameterized_argument_value_pairs(func, p) [("foo", 1)] >>> p = param(1, 16) >>> parameterized_argument_value_pairs(func, p) [("foo", 1), ("*args", (16, ))] """ argspec = getargspec(func) arg_offset = 1 if argspec.args[:1] == ["self"] else 0 named_args = argspec.args[arg_offset:] result = lzip(named_args, p.args) named_args = argspec.args[len(result) + arg_offset:] varargs = p.args[len(result):] result.extend([ (name, p.kwargs.get(name, default)) for (name, default) in zip(named_args, argspec.defaults or []) ]) seen_arg_names = set([ n for (n, _) in result ]) keywords = QuietOrderedDict(sorted([ (name, p.kwargs[name]) for name in p.kwargs if name not in seen_arg_names ])) if varargs: result.append(("*%s" %(argspec.varargs, ), tuple(varargs))) if keywords: result.append(("**%s" %(argspec.keywords, ), keywords)) return result def short_repr(x, n=64): """ A shortened repr of ``x`` which is guaranteed to be ``unicode``:: >>> short_repr("foo") u"foo" >>> short_repr("123456789", n=4) u"12...89" """ x_repr = to_text(repr(x)) if len(x_repr) > n: x_repr = x_repr[:n//2] + "..." + x_repr[len(x_repr) - n//2:] return x_repr def default_doc_func(func, num, p): if func.__doc__ is None: return None all_args_with_values = parameterized_argument_value_pairs(func, p) # Assumes that the function passed is a bound method. descs = ["%s=%s" %(n, short_repr(v)) for n, v in all_args_with_values] # The documentation might be a multiline string, so split it # and just work with the first string, ignoring the period # at the end if there is one. first, nl, rest = func.__doc__.lstrip().partition("\n") suffix = "" if first.endswith("."): suffix = "." first = first[:-1] args = "%s[with %s]" %(len(first) and " " or "", ", ".join(descs)) return "".join( to_text(x) for x in [first.rstrip(), args, suffix, nl, rest] ) def default_name_func(func, num, p): base_name = func.__name__ name_suffix = "_%s" %(num, ) if len(p.args) > 0 and isinstance(p.args[0], string_types): name_suffix += "_" + parameterized.to_safe_name(p.args[0]) return base_name + name_suffix _test_runner_override = None _test_runner_guess = False _test_runners = set(["unittest", "unittest2", "nose", "nose2", "pytest"]) _test_runner_aliases = { "_pytest": "pytest", } def set_test_runner(name): global _test_runner_override if name not in _test_runners: raise TypeError( "Invalid test runner: %r (must be one of: %s)" %(name, ", ".join(_test_runners)), ) _test_runner_override = name def detect_runner(): """ Guess which test runner we're using by traversing the stack and looking for the first matching module. This *should* be reasonably safe, as it's done during test disocvery where the test runner should be the stack frame immediately outside. """ if _test_runner_override is not None: return _test_runner_override global _test_runner_guess if _test_runner_guess is False: stack = inspect.stack() for record in reversed(stack): frame = record[0] module = frame.f_globals.get("__name__").partition(".")[0] if module in _test_runner_aliases: module = _test_runner_aliases[module] if module in _test_runners: _test_runner_guess = module break if record[1].endswith("python2.6/unittest.py"): _test_runner_guess = "unittest" break else: _test_runner_guess = None return _test_runner_guess class parameterized(object): """ Parameterize a test case:: class TestInt(object): @parameterized([ ("A", 10), ("F", 15), param("10", 42, base=42) ]) def test_int(self, input, expected, base=16): actual = int(input, base=base) assert_equal(actual, expected) @parameterized([ (2, 3, 5) (3, 5, 8), ]) def test_add(a, b, expected): assert_equal(a + b, expected) """ def __init__(self, input, doc_func=None, skip_on_empty=False): self.get_input = self.input_as_callable(input) self.doc_func = doc_func or default_doc_func self.skip_on_empty = skip_on_empty def __call__(self, test_func): self.assert_not_in_testcase_subclass() @wraps(test_func) def wrapper(test_self=None): test_cls = test_self and type(test_self) if test_self is not None: if issubclass(test_cls, InstanceType): raise TypeError(( "@parameterized can't be used with old-style classes, but " "%r has an old-style class. Consider using a new-style " "class, or '@parameterized.expand' " "(see http://stackoverflow.com/q/54867/71522 for more " "information on old-style classes)." ) %(test_self, )) original_doc = wrapper.__doc__ for num, args in enumerate(wrapper.parameterized_input): p = param.from_decorator(args) unbound_func, nose_tuple = self.param_as_nose_tuple(test_self, test_func, num, p) try: wrapper.__doc__ = nose_tuple[0].__doc__ # Nose uses `getattr(instance, test_func.__name__)` to get # a method bound to the test instance (as opposed to a # method bound to the instance of the class created when # tests were being enumerated). Set a value here to make # sure nose can get the correct test method. if test_self is not None: setattr(test_cls, test_func.__name__, unbound_func) yield nose_tuple finally: if test_self is not None: delattr(test_cls, test_func.__name__) wrapper.__doc__ = original_doc input = self.get_input() if not input: if not self.skip_on_empty: raise ValueError( "Parameters iterable is empty (hint: use " "`parameterized([], skip_on_empty=True)` to skip " "this test when the input is empty)" ) wrapper = wraps(test_func)(skip_on_empty_helper) wrapper.parameterized_input = input wrapper.parameterized_func = test_func test_func.__name__ = "_parameterized_original_%s" %(test_func.__name__, ) return wrapper def param_as_nose_tuple(self, test_self, func, num, p): nose_func = wraps(func)(lambda *args: func(*args[:-1], **args[-1])) nose_func.__doc__ = self.doc_func(func, num, p) # Track the unbound function because we need to setattr the unbound # function onto the class for nose to work (see comments above), and # Python 3 doesn't let us pull the function out of a bound method. unbound_func = nose_func if test_self is not None: # Under nose on Py2 we need to return an unbound method to make # sure that the `self` in the method is properly shared with the # `self` used in `setUp` and `tearDown`. But only there. Everyone # else needs a bound method. func_self = ( None if PY2 and detect_runner() == "nose" else test_self ) nose_func = make_method(nose_func, func_self, type(test_self)) return unbound_func, (nose_func, ) + p.args + (p.kwargs or {}, ) def assert_not_in_testcase_subclass(self): parent_classes = self._terrible_magic_get_defining_classes() if any(issubclass(cls, TestCase) for cls in parent_classes): raise Exception("Warning: '@parameterized' tests won't work " "inside subclasses of 'TestCase' - use " "'@parameterized.expand' instead.") def _terrible_magic_get_defining_classes(self): """ Returns the set of parent classes of the class currently being defined. Will likely only work if called from the ``parameterized`` decorator. This function is entirely @brandon_rhodes's fault, as he suggested the implementation: http://stackoverflow.com/a/8793684/71522 """ stack = inspect.stack() if len(stack) <= 4: return [] frame = stack[4] code_context = frame[4] and frame[4][0].strip() if not (code_context and code_context.startswith("class ")): return [] _, _, parents = code_context.partition("(") parents, _, _ = parents.partition(")") return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals) @classmethod def input_as_callable(cls, input): if callable(input): return lambda: cls.check_input_values(input()) input_values = cls.check_input_values(input) return lambda: input_values @classmethod def check_input_values(cls, input_values): # Explicitly convery non-list inputs to a list so that: # 1. A helpful exception will be raised if they aren't iterable, and # 2. Generators are unwrapped exactly once (otherwise `nosetests # --processes=n` has issues; see: # https://github.com/wolever/nose-parameterized/pull/31) if not isinstance(input_values, list): input_values = list(input_values) return [ param.from_decorator(p) for p in input_values ] @classmethod def expand(cls, input, name_func=None, doc_func=None, skip_on_empty=False, **legacy): """ A "brute force" method of parameterizing test cases. Creates new test cases and injects them into the namespace that the wrapped function is being defined in. Useful for parameterizing tests in subclasses of 'UnitTest', where Nose test generators don't work. >>> @parameterized.expand([("foo", 1, 2)]) ... def test_add1(name, input, expected): ... actual = add1(input) ... assert_equal(actual, expected) ... >>> locals() ... 'test_add1_foo_0': ... >>> """ if "testcase_func_name" in legacy: warnings.warn("testcase_func_name= is deprecated; use name_func=", DeprecationWarning, stacklevel=2) if not name_func: name_func = legacy["testcase_func_name"] if "testcase_func_doc" in legacy: warnings.warn("testcase_func_doc= is deprecated; use doc_func=", DeprecationWarning, stacklevel=2) if not doc_func: doc_func = legacy["testcase_func_doc"] doc_func = doc_func or default_doc_func name_func = name_func or default_name_func def parameterized_expand_wrapper(f, instance=None): frame_locals = inspect.currentframe().f_back.f_locals parameters = cls.input_as_callable(input)() if not parameters: if not skip_on_empty: raise ValueError( "Parameters iterable is empty (hint: use " "`parameterized.expand([], skip_on_empty=True)` to skip " "this test when the input is empty)" ) return wraps(f)(skip_on_empty_helper) digits = len(str(len(parameters) - 1)) for num, p in enumerate(parameters): name = name_func(f, "{num:0>{digits}}".format(digits=digits, num=num), p) # If the original function has patches applied by 'mock.patch', # re-construct all patches on the just former decoration layer # of param_as_standalone_func so as not to share # patch objects between new functions nf = reapply_patches_if_need(f) frame_locals[name] = cls.param_as_standalone_func(p, nf, name) frame_locals[name].__doc__ = doc_func(f, num, p) # Delete original patches to prevent new function from evaluating # original patching object as well as re-constructed patches. delete_patches_if_need(f) f.__test__ = False return parameterized_expand_wrapper @classmethod def param_as_standalone_func(cls, p, func, name): @wraps(func) def standalone_func(*a): return func(*(a + p.args), **p.kwargs) standalone_func.__name__ = name # place_as is used by py.test to determine what source file should be # used for this test. standalone_func.place_as = func # Remove __wrapped__ because py.test will try to look at __wrapped__ # to determine which parameters should be used with this test case, # and obviously we don't need it to do any parameterization. try: del standalone_func.__wrapped__ except AttributeError: pass return standalone_func @classmethod def to_safe_name(cls, s): return str(re.sub("[^a-zA-Z0-9_]+", "_", s)) def parameterized_class(attrs, input_values=None, class_name_func=None, classname_func=None): """ Parameterizes a test class by setting attributes on the class. Can be used in two ways: 1) With a list of dictionaries containing attributes to override:: @parameterized_class([ { "username": "foo" }, { "username": "bar", "access_level": 2 }, ]) class TestUserAccessLevel(TestCase): ... 2) With a tuple of attributes, then a list of tuples of values: @parameterized_class(("username", "access_level"), [ ("foo", 1), ("bar", 2) ]) class TestUserAccessLevel(TestCase): ... """ if isinstance(attrs, string_types): attrs = [attrs] input_dicts = ( attrs if input_values is None else [dict(zip(attrs, vals)) for vals in input_values] ) class_name_func = class_name_func or default_class_name_func if classname_func: warnings.warn( "classname_func= is deprecated; use class_name_func= instead. " "See: https://github.com/wolever/parameterized/pull/74#issuecomment-613577057", DeprecationWarning, stacklevel=2, ) class_name_func = lambda cls, idx, input: classname_func(cls, idx, input_dicts) def decorator(base_class): test_class_module = sys.modules[base_class.__module__].__dict__ for idx, input_dict in enumerate(input_dicts): test_class_dict = dict(base_class.__dict__) test_class_dict.update(input_dict) name = class_name_func(base_class, idx, input_dict) test_class_module[name] = type(name, (base_class, ), test_class_dict) # We need to leave the base class in place (see issue #73), but if we # leave the test_ methods in place, the test runner will try to pick # them up and run them... which doesn't make sense, since no parameters # will have been applied. # Address this by iterating over the base class and remove all test # methods. for method_name in list(base_class.__dict__): if method_name.startswith("test"): delattr(base_class, method_name) return base_class return decorator def get_class_name_suffix(params_dict): if "name" in params_dict: return parameterized.to_safe_name(params_dict["name"]) params_vals = ( params_dict.values() if PY3 else (v for (_, v) in sorted(params_dict.items())) ) return parameterized.to_safe_name(next(( v for v in params_vals if isinstance(v, string_types) ), "")) def default_class_name_func(cls, num, params_dict): suffix = get_class_name_suffix(params_dict) return "%s_%s%s" %( cls.__name__, num, suffix and "_" + suffix, ) parameterized-0.8.1/.hgignore0000644000076600001200000000022412075364747016765 0ustar woleveradmin00000000000000syntax: glob *.class *.o *.pyc *.sqlite3 *.sw[op] *~ .DS_Store bin-debug/* bin-release/* bin/* tags *.beam *.dump env/ *egg-info* dist/ .tox .env32 parameterized-0.8.1/setup.py0000644000076600001200000000164013776411255016673 0ustar woleveradmin00000000000000#!/usr/bin/env python import io import os import sys from setuptools import setup, find_packages import parameterized os.chdir(os.path.dirname(sys.argv[0]) or ".") try: long_description = io.open("README.rst", encoding="utf-8").read() except IOError: long_description = "See https://github.com/wolever/parameterized" setup( name="parameterized", version=parameterized.__version__, url="https://github.com/wolever/parameterized", license="FreeBSD", author="David Wolever", author_email="david@wolever.net", description="Parameterized testing with any Python test framework", classifiers=[ 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'License :: OSI Approved :: BSD License', ], packages=find_packages(), extras_require={ 'dev': [ 'jinja2', ] }, long_description=long_description, ) parameterized-0.8.1/.gitignore0000644000076600001200000000025213442612250017133 0ustar woleveradmin00000000000000*.class *.o *.pyc *.sqlite3 *.sw[op] *~ .DS_Store bin-debug/* bin-release/* bin/* tags *.beam *.dump env/ .env/ *egg-info* misc/ dist/ Icon? .tox build/ .cache/ \.idea/ parameterized-0.8.1/tox.ini0000644000076600001200000000101713644701444016466 0ustar woleveradmin00000000000000[tox] envlist=py{27,35,36,py}-{nose,nose2,pytest2,pytest3,unit,unit2},py{37,38,39}-{nose,nose2,pytest3,unit,unit2} [testenv] deps= nose mock nose2: nose2 pytest2: pytest>=2,<3 pytest3: pytest>=3,<4 #pytest4: pytest>=4,<5 unit2: unittest2 commands= nose: nosetests nose2: nose2 pytest2: py.test parameterized/test.py pytest3: py.test parameterized/test.py #pytest4: py.test parameterized/test.py unit: python -m unittest parameterized.test unit2: unit2 parameterized.test parameterized-0.8.1/CHANGELOG.txt0000644000076600001200000001203013776411416017203 0ustar woleveradmin000000000000000.8.1 (2021-01-09) * Add README and LICENSE to pypi sdist package (https://github.com/wolever/parameterized/pull/114; thanks @chohner) 0.8.0 (2021-01-04) * Handle camelCase test names. This is an ever-so-slightly backwards incompatible change, as the method for determining which methods are test methods has changed from ``method_name.startswith("test_")`` to ``method_name.startswith("test")``. The latter is the behavior of the ``unittest`` module. (thanks @bobwalker99; https://github.com/wolever/parameterized/pull/106) * Fix arguments to skip_on_empty helper (thanks @bhavyakjain; https://github.com/wolever/parameterized/pull/104) 0.7.5 (2021-01-04) * Fix crash on Python 2.7 with unicode docstrings (https://github.com/wolever/parameterized/issues/109) * Replace inspect.stack() with faster inspect.currentframe() (https://github.com/wolever/parameterized/issues/107) * Add a __version__ string to the package (https://github.com/wolever/parameterized/issues/103) 0.7.4 (2020-04-14) * Add ``class_name_func`` option to ``@parameterized_class`` * Fix arguments being passed to skip_on_empty_helper (thanks @gaf3; https://github.com/wolever/parameterized/issues/57) * Fix tests on Python 3.8.2 (thanks @Ikke; https://github.com/wolever/parameterized/pull/99) 0.7.3 (2020-04-12) * Fix some typos (thanks @LWprogramming, @jinnovation; https://github.com/wolever/parameterized/pull/77, https://github.com/wolever/parameterized/pull/80) * Fix unicode handling in setup.py (thanks @sliwinski-milosz; https://github.com/wolever/parameterized/pull/89) * Fix Python 3.9 compat (thanks @vstinner; https://github.com/wolever/parameterized/pull/94) 0.7.2 (2020-04-12) * Add `@parameterized_class` name function callback support (thanks @duncwebb et al; https://github.com/wolever/parameterized/pull/74) 0.7.1 (2019-11-19) * Fix #73: calling `setUp` and `tearDown` in parameterized classes 0.7.0 (2019-02-05) * Added parameterized_class feature, for parameterizing entire test classes (many thanks to @TobyLL for their suggestions and help testing!) * Fix DeprecationWarning on `inspect.getargs` (thanks @brettdh; https://github.com/wolever/parameterized/issues/67) 0.6.2 (2018-03-11) * Make sure that `setUp` and `tearDown` methods work correctly (#40) * Raise a ValueError when input is empty (thanks @danielbradburn; https://github.com/wolever/parameterized/pull/48) * Fix the order when number of cases exceeds 10 (thanks @ntflc; https://github.com/wolever/parameterized/pull/49) 0.6.1 (2017-03-21) * Rename package from nose-parameterized to parameterized. A nose-parameterized package will be released with a deprecation warning. * Rename testcase_func_doc and testcase_func_name methods to doc_func and name_func (a DeprecationWarning will be issued, to be removed in 1.0). * Include parameters in all docstrings, not just `parameterized.expand` docstrings. * Explicitly unwrap iterators and generators before the test run (thanks @chornsby; https://github.com/wolever/nose-parameterized/pull/31) * 0.6.1 instead of 0.6.0 because I'm a dumb and accidentally uploaded the wrong thing to PyPI under version 0.6.0. 0.5.0 (2015-06-09) * Support for nose2, py.test, unittest, and unittest2 (nose2 support thanks to @marek-mazur; https://github.com/wolever/nose-parameterized/pull/26) 0.4.2 (2015-05-18) * Fix bug with expand + empty arguments (thanks @jikamens; https://github.com/wolever/nose-parameterized/pull/25) 0.4.1 (2015-05-17) * Fix bug with expand + empty docstring (thanks @jikamens; https://github.com/wolever/nose-parameterized/pull/24) 0.4.0 (2015-05-11) * Include parameters in ``parameterized.expand`` function docstrings (https://github.com/wolever/nose-parameterized/pull/22; thanks @smspillaz) * Drop Python 3.2 support 0.3.5 (2014-11-05) * Allow the names of test cases generated by ``parameterized.expand`` to be customized. (https://github.com/wolever/nose-parameterized/pull/19; thanks @curtissiemens) 0.3.4 (2014-10-03) * Use ``functools.wraps`` to wrap expanded functions (https://github.com/wolever/nose-parameterized/pull/17; thanks @toumorokoshi) 0.3.3 (2014-01-03) * Replace unsafe characters with "_" in names generated by ``@parameterized.expand``. 0.3.2 (2014-01-02) * Add helpful error message when used with old-style classes. 0.3.1 (2013-08-01) * Fix bug: `nose_parameterized.param` wasn't being imported. 0.3 (2013-05-18) * Add `param` class. * Add explicit support for callable inputs. * Update readme to more throughly describe useage. * Remove un-used test helpers (`setup_logging`, `teardown_logging`, `logged_messages`, `assert_logged`, `assert_no_errors_logged`, `assert_contains`, `assert_not_contains`, `assert_raises`, `imported_from_test`). 0.2 (2013-01-15) * Add Python 3 support parameterized-0.8.1/setup.cfg0000644000076600001200000000020213776411510016765 0ustar woleveradmin00000000000000[wheel] universal = 1 [metadata] description-file = README.rst license_file = LICENSE.txt [egg_info] tag_build = tag_date = 0 parameterized-0.8.1/README.rst0000644000076600001200000004324513645371201016646 0ustar woleveradmin00000000000000Parameterized testing with any Python test framework ==================================================== .. image:: https://img.shields.io/pypi/v/parameterized.svg :alt: PyPI :target: https://pypi.org/project/parameterized/ .. image:: https://circleci.com/gh/wolever/parameterized.svg?style=svg :alt: Circle CI :target: https://circleci.com/gh/wolever/parameterized Parameterized testing in Python sucks. ``parameterized`` fixes that. For everything. Parameterized testing for nose, parameterized testing for py.test, parameterized testing for unittest. .. code:: python # test_math.py from nose.tools import assert_equal from parameterized import parameterized, parameterized_class import unittest import math @parameterized([ (2, 2, 4), (2, 3, 8), (1, 9, 1), (0, 9, 0), ]) def test_pow(base, exponent, expected): assert_equal(math.pow(base, exponent), expected) class TestMathUnitTest(unittest.TestCase): @parameterized.expand([ ("negative", -1.5, -2.0), ("integer", 1, 1.0), ("large fraction", 1.6, 1), ]) def test_floor(self, name, input, expected): assert_equal(math.floor(input), expected) @parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [ (1, 2, 3, 2), (5, 5, 10, 25), ]) class TestMathClass(unittest.TestCase): def test_add(self): assert_equal(self.a + self.b, self.expected_sum) def test_multiply(self): assert_equal(self.a * self.b, self.expected_product) @parameterized_class([ { "a": 3, "expected": 2 }, { "b": 5, "expected": -4 }, ]) class TestMathClassDict(unittest.TestCase): a = 1 b = 1 def test_subtract(self): assert_equal(self.a - self.b, self.expected) With nose (and nose2):: $ nosetests -v test_math.py test_floor_0_negative (test_math.TestMathUnitTest) ... ok test_floor_1_integer (test_math.TestMathUnitTest) ... ok test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok test_math.test_pow(2, 2, 4, {}) ... ok test_math.test_pow(2, 3, 8, {}) ... ok test_math.test_pow(1, 9, 1, {}) ... ok test_math.test_pow(0, 9, 0, {}) ... ok test_add (test_math.TestMathClass_0) ... ok test_multiply (test_math.TestMathClass_0) ... ok test_add (test_math.TestMathClass_1) ... ok test_multiply (test_math.TestMathClass_1) ... ok test_subtract (test_math.TestMathClassDict_0) ... ok ---------------------------------------------------------------------- Ran 12 tests in 0.015s OK As the package name suggests, nose is best supported and will be used for all further examples. With py.test (version 2.0 and above):: $ py.test -v test_math.py ============================= test session starts ============================== platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0 collecting ... collected 13 items test_math.py::test_pow::[0] PASSED test_math.py::test_pow::[1] PASSED test_math.py::test_pow::[2] PASSED test_math.py::test_pow::[3] PASSED test_math.py::TestMathUnitTest::test_floor_0_negative PASSED test_math.py::TestMathUnitTest::test_floor_1_integer PASSED test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED test_math.py::TestMathClass_0::test_add PASSED test_math.py::TestMathClass_0::test_multiply PASSED test_math.py::TestMathClass_1::test_add PASSED test_math.py::TestMathClass_1::test_multiply PASSED test_math.py::TestMathClassDict_0::test_subtract PASSED ==================== 12 passed, 4 warnings in 0.16 seconds ===================== With unittest (and unittest2):: $ python -m unittest -v test_math test_floor_0_negative (test_math.TestMathUnitTest) ... ok test_floor_1_integer (test_math.TestMathUnitTest) ... ok test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok test_add (test_math.TestMathClass_0) ... ok test_multiply (test_math.TestMathClass_0) ... ok test_add (test_math.TestMathClass_1) ... ok test_multiply (test_math.TestMathClass_1) ... ok test_subtract (test_math.TestMathClassDict_0) ... ok ---------------------------------------------------------------------- Ran 8 tests in 0.001s OK (note: because unittest does not support test decorators, only tests created with ``@parameterized.expand`` will be executed) With green:: $ green test_math.py -vvv test_math TestMathClass_1 . test_method_a . test_method_b TestMathClass_2 . test_method_a . test_method_b TestMathClass_3 . test_method_a . test_method_b TestMathUnitTest . test_floor_0_negative . test_floor_1_integer . test_floor_2_large_fraction TestMathClass_0 . test_add . test_multiply TestMathClass_1 . test_add . test_multiply TestMathClassDict_0 . test_subtract Ran 12 tests in 0.121s OK (passes=9) Installation ------------ :: $ pip install parameterized Compatibility ------------- `Yes`__ (mostly). __ https://travis-ci.org/wolever/parameterized .. list-table:: :header-rows: 1 :stub-columns: 1 * - - Py2.6 - Py2.7 - Py3.4 - Py3.5 - Py3.6 - Py3.7 - Py3.8 - Py3.9 - PyPy - ``@mock.patch`` * - nose - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - nose2 - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - py.test 2 - yes - yes - no* - no* - no* - no* - yes - yes - yes - yes * - py.test 3 - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - py.test 4 - no** - no** - no** - no** - no** - no** - no** - no** - no** - no** * - py.test fixtures - no† - no† - no† - no† - no† - no† - no† - no† - no† - no† * - | unittest | (``@parameterized.expand``) - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes * - | unittest2 | (``@parameterized.expand``) - yes - yes - yes - yes - yes - yes - yes - yes - yes - yes \*: py.test 2 does `does not appear to work (#71)`__ under Python 3. Please comment on the related issues if you are affected. \*\*: py.test 4 is not yet supported (but coming!) in `issue #34`__ †: py.test fixture support is documented in `issue #81`__ __ https://github.com/wolever/parameterized/issues/71 __ https://github.com/wolever/parameterized/issues/34 __ https://github.com/wolever/parameterized/issues/81 Dependencies ------------ (this section left intentionally blank) Exhaustive Usage Examples -------------------------- The ``@parameterized`` and ``@parameterized.expand`` decorators accept a list or iterable of tuples or ``param(...)``, or a callable which returns a list or iterable: .. code:: python from parameterized import parameterized, param # A list of tuples @parameterized([ (2, 3, 5), (3, 5, 8), ]) def test_add(a, b, expected): assert_equal(a + b, expected) # A list of params @parameterized([ param("10", 10), param("10", 16, base=16), ]) def test_int(str_val, expected, base=10): assert_equal(int(str_val, base=base), expected) # An iterable of params @parameterized( param.explicit(*json.loads(line)) for line in open("testcases.jsons") ) def test_from_json_file(...): ... # A callable which returns a list of tuples def load_test_cases(): return [ ("test1", ), ("test2", ), ] @parameterized(load_test_cases) def test_from_function(name): ... .. ** Note that, when using an iterator or a generator, all the items will be loaded into memory before the start of the test run (we do this explicitly to ensure that generators are exhausted exactly once in multi-process or multi-threaded testing environments). The ``@parameterized`` decorator can be used test class methods, and standalone functions: .. code:: python from parameterized import parameterized class AddTest(object): @parameterized([ (2, 3, 5), ]) def test_add(self, a, b, expected): assert_equal(a + b, expected) @parameterized([ (2, 3, 5), ]) def test_add(a, b, expected): assert_equal(a + b, expected) And ``@parameterized.expand`` can be used to generate test methods in situations where test generators cannot be used (for example, when the test class is a subclass of ``unittest.TestCase``): .. code:: python import unittest from parameterized import parameterized class AddTestCase(unittest.TestCase): @parameterized.expand([ ("2 and 3", 2, 3, 5), ("3 and 5", 2, 3, 5), ]) def test_add(self, _, a, b, expected): assert_equal(a + b, expected) Will create the test cases:: $ nosetests example.py test_add_0_2_and_3 (example.AddTestCase) ... ok test_add_1_3_and_5 (example.AddTestCase) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK Note that ``@parameterized.expand`` works by creating new methods on the test class. If the first parameter is a string, that string will be added to the end of the method name. For example, the test case above will generate the methods ``test_add_0_2_and_3`` and ``test_add_1_3_and_5``. The names of the test cases generated by ``@parameterized.expand`` can be customized using the ``name_func`` keyword argument. The value should be a function which accepts three arguments: ``testcase_func``, ``param_num``, and ``params``, and it should return the name of the test case. ``testcase_func`` will be the function to be tested, ``param_num`` will be the index of the test case parameters in the list of parameters, and ``param`` (an instance of ``param``) will be the parameters which will be used. .. code:: python import unittest from parameterized import parameterized def custom_name_func(testcase_func, param_num, param): return "%s_%s" %( testcase_func.__name__, parameterized.to_safe_name("_".join(str(x) for x in param.args)), ) class AddTestCase(unittest.TestCase): @parameterized.expand([ (2, 3, 5), (2, 3, 5), ], name_func=custom_name_func) def test_add(self, a, b, expected): assert_equal(a + b, expected) Will create the test cases:: $ nosetests example.py test_add_1_2_3 (example.AddTestCase) ... ok test_add_2_3_5 (example.AddTestCase) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK The ``param(...)`` helper class stores the parameters for one specific test case. It can be used to pass keyword arguments to test cases: .. code:: python from parameterized import parameterized, param @parameterized([ param("10", 10), param("10", 16, base=16), ]) def test_int(str_val, expected, base=10): assert_equal(int(str_val, base=base), expected) If test cases have a docstring, the parameters for that test case will be appended to the first line of the docstring. This behavior can be controlled with the ``doc_func`` argument: .. code:: python from parameterized import parameterized @parameterized([ (1, 2, 3), (4, 5, 9), ]) def test_add(a, b, expected): """ Test addition. """ assert_equal(a + b, expected) def my_doc_func(func, num, param): return "%s: %s with %s" %(num, func.__name__, param) @parameterized([ (5, 4, 1), (9, 6, 3), ], doc_func=my_doc_func) def test_subtraction(a, b, expected): assert_equal(a - b, expected) :: $ nosetests example.py Test addition. [with a=1, b=2, expected=3] ... ok Test addition. [with a=4, b=5, expected=9] ... ok 0: test_subtraction with param(*(5, 4, 1)) ... ok 1: test_subtraction with param(*(9, 6, 3)) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK Finally ``@parameterized_class`` parameterizes an entire class, using either a list of attributes, or a list of dicts that will be applied to the class: .. code:: python from yourapp.models import User from parameterized import parameterized_class @parameterized_class([ { "username": "user_1", "access_level": 1 }, { "username": "user_2", "access_level": 2, "expected_status_code": 404 }, ]) class TestUserAccessLevel(TestCase): expected_status_code = 200 def setUp(self): self.client.force_login(User.objects.get(username=self.username)[0]) def test_url_a(self): response = self.client.get('/url') self.assertEqual(response.status_code, self.expected_status_code) def tearDown(self): self.client.logout() @parameterized_class(("username", "access_level", "expected_status_code"), [ ("user_1", 1, 200), ("user_2", 2, 404) ]) class TestUserAccessLevel(TestCase): def setUp(self): self.client.force_login(User.objects.get(username=self.username)[0]) def test_url_a(self): response = self.client.get("/url") self.assertEqual(response.status_code, self.expected_status_code) def tearDown(self): self.client.logout() The ``@parameterized_class`` decorator accepts a ``class_name_func`` argument, which controls the name of the parameterized classes generated by ``@parameterized_class``: .. code:: python from parameterized import parameterized, parameterized_class def get_class_name(cls, num, params_dict): # By default the generated class named includes either the "name" # parameter (if present), or the first string value. This example shows # multiple parameters being included in the generated class name: return "%s_%s_%s%s" %( cls.__name__, num, parameterized.to_safe_name(params_dict['a']), parameterized.to_safe_name(params_dict['b']), ) @parameterized_class([ { "a": "hello", "b": " world!", "expected": "hello world!" }, { "a": "say ", "b": " cheese :)", "expected": "say cheese :)" }, ], class_name_func=get_class_name) class TestConcatenation(TestCase): def test_concat(self): self.assertEqual(self.a + self.b, self.expected) :: $ nosetests -v test_math.py test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok Using with Single Parameters ............................ If a test function only accepts one parameter and the value is not iterable, then it is possible to supply a list of values without wrapping each one in a tuple: .. code:: python @parameterized([1, 2, 3]) def test_greater_than_zero(value): assert value > 0 Note, however, that if the single parameter *is* iterable (such as a list or tuple), then it *must* be wrapped in a tuple, list, or the ``param(...)`` helper: .. code:: python @parameterized([ ([1, 2, 3], ), ([3, 3], ), ([6], ), ]) def test_sums_to_6(numbers): assert sum(numbers) == 6 (note, also, that Python requires single element tuples to be defined with a trailing comma: ``(foo, )``) Using with ``@mock.patch`` .......................... ``parameterized`` can be used with ``mock.patch``, but the argument ordering can be confusing. The ``@mock.patch(...)`` decorator must come *below* the ``@parameterized(...)``, and the mocked parameters must come *last*: .. code:: python @mock.patch("os.getpid") class TestOS(object): @parameterized(...) @mock.patch("os.fdopen") @mock.patch("os.umask") def test_method(self, param1, param2, ..., mock_umask, mock_fdopen, mock_getpid): ... Note: the same holds true when using ``@parameterized.expand``. Migrating from ``nose-parameterized`` to ``parameterized`` ---------------------------------------------------------- To migrate a codebase from ``nose-parameterized`` to ``parameterized``: 1. Update your requirements file, replacing ``nose-parameterized`` with ``parameterized``. 2. Replace all references to ``nose_parameterized`` with ``parameterized``:: $ perl -pi -e 's/nose_parameterized/parameterized/g' your-codebase/ 3. You're done! FAQ --- What happened to ``nose-parameterized``? Originally only nose was supported. But now everything is supported, and it only made sense to change the name! What do you mean when you say "nose is best supported"? There are small caveates with ``py.test`` and ``unittest``: ``py.test`` does not show the parameter values (ex, it will show ``test_add[0]`` instead of ``test_add[1, 2, 3]``), and ``unittest``/``unittest2`` do not support test generators so ``@parameterized.expand`` must be used. Why not use ``@pytest.mark.parametrize``? Because spelling is difficult. Also, ``parameterized`` doesn't require you to repeat argument names, and (using ``param``) it supports optional keyword arguments. Why do I get an ``AttributeError: 'function' object has no attribute 'expand'`` with ``@parameterized.expand``? You've likely installed the ``parametrized`` (note the missing *e*) package. Use ``parameterized`` (with the *e*) instead and you'll be all set. parameterized-0.8.1/LICENSE.txt0000644000076600001200000000302613644705032016775 0ustar woleveradmin00000000000000Unless stated otherwise in the source files, all code is copyright 2010 David Wolever . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY DAVID WOLEVER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID WOLEVER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of David Wolever. parameterized-0.8.1/.travis.yml0000644000076600001200000000000013644701444017253 0ustar woleveradmin00000000000000parameterized-0.8.1/misspelling-helper/0000755000076600001200000000000013776411510020755 5ustar woleveradmin00000000000000parameterized-0.8.1/misspelling-helper/nose-parametrized/0000755000076600001200000000000013776411510024406 5ustar woleveradmin00000000000000parameterized-0.8.1/misspelling-helper/nose-parametrized/MANIFEST0000644000076600001200000000006413065521622025534 0ustar woleveradmin00000000000000# file GENERATED by distutils, do NOT edit setup.py parameterized-0.8.1/misspelling-helper/nose-parametrized/setup.py0000644000076600001200000000065513442612234026122 0ustar woleveradmin00000000000000import sys from distutils.core import setup if __name__ == "__main__": if "sdist" not in sys.argv[1:]: raise ValueError("Please use the 'parameterized' (note the second 'e' in 'parameterized') pypi package instead of 'nose-parametrized'") setup( name="nose-parametrized", version="0.1", description="Please use the 'parameterized' package (note the second 'e' in 'parameterized')", )