Give AlbumentationsX a star on GitHub — it powers this leaderboard

Star on GitHub

parameterized

Parameterized testing with any Python test framework

Downloads: 0 (30 days)

Description

Parameterized testing with any Python test framework
====================================================

.. image:: https://img.shields.io/pypi/v/parameterized
    :alt: PyPI
    :target: https://pypi.org/project/parameterized/

.. image:: https://img.shields.io/pypi/dm/parameterized
    :alt: PyPI - Downloads
    :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://app.circleci.com/pipelines/github/wolever/parameterized?branch=master

.. list-table::
   :header-rows: 1
   :stub-columns: 1

   * -
     - Py3.7
     - Py3.8
     - Py3.9
     - Py3.10
     - Py3.11
     - PyPy3
     - ``@mock.patch``
   * - nose
     - yes
     - yes
     - yes
     - yes
     - no§
     - no§
     - yes
   * - nose2
     - yes
     - yes
     - yes
     - yes
     - yes
     - yes
     - yes
   * - py.test 2
     - no*
     - no*
     - no*
     - no*
     - no*
     - no*
     - no*
   * - py.test 3
     - yes
     - yes
     - yes
     - yes
     - no*
     - no*
     - yes
   * - py.test 4
     - no**
     - no**
     - no**
     - no**
     - no**
     - no**
     - no**
   * - py.test fixtures
     - no†
     - no†
     - no†
     - no†
     - no†
     - no†
     - no†
   * - | unittest
       | (``@parameterized.expand``)
     - yes
     - yes
     - yes
     - yes
     - yes
     - yes
     - yes
   * - | unittest2
       | (``@parameterized.expand``)
     - yes
     - yes
     - yes
     - yes
     - no§
     - no§
     - yes

§: nose and unittest2 - both of which were last updated in 2015 - sadly do not
appear to support Python 3.10 or 3.11.

\*: `py.test 2 does not appear to work under Python 3 (#71)`__, and
`py.test 3 does not appear to work under Python 3.10 or 3.11 (#154)`__.

\*\*: 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/154
__ 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", 3, 5, 8),
        ])
        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 b