Skip to main content

Drop-in @pytest.mark.parametrize replacement working with unittest.TestCase

Project description

CI PyPi Python Versions Code style: black

Drop-in @pytest.mark.parametrize replacement working with unittest.TestCase

Why?

You want to start using @pytest.mark.parametrize, but can't simply drop unittest.TestCase because you have tons of self.assert's, setUp's tearDown's to rewrite?

With @parametrize you can start parameterizing your tests now, and get rid of unittest.TestCase later if needed.

Usage

Simple example from pytest docs adapted to unittest

import unittest
from parametrize import parametrize

class TestSomething(unittest.TestCase):

    @parametrize('test_input,expected', [("3+5", 8), ("2+4", 6)])
    def test_eval(self, test_input, expected):
        self.assertEqual(expected, eval(test_input))
$ python -m unittest test.py -v
test_eval[2+4-6] (test.TestSomething) ... ok
test_eval[3+5-8] (test.TestSomething) ... ok
test_eval[6*9-42] (test.TestSomething) ... FAIL

======================================================================
FAIL: test_eval[6*9-42] (test.TestSomething)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "parametrize/parametrize.py", line 261, in parametrized_method
    return parametrized_func(*args, **kwargs)
  File "test.py", line 8, in test_eval
    self.assertEqual(expected, eval(test_input))
AssertionError: 42 != 54

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)
You don't need to use additional decorators, custom base classes or metaclasses.

Stacking parametrize decorators:

import unittest
from parametrize import parametrize

class TestSomething(unittest.TestCase):
    
    @parametrize("x", [0, 1])
    @parametrize("y", [2, 3])
    def test_foo(self, x, y):
        pass

test_foo will be called with: (x=0, y=2), (x=1, y=2), (x=0, y=3), and (x=1, y=3):

$ python -m unittest test.py -v
test_foo[2-0] (test.TestSomething) ... ok
test_foo[2-1] (test.TestSomething) ... ok
test_foo[3-0] (test.TestSomething) ... ok
test_foo[3-1] (test.TestSomething) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK
Note: even though the tests are always generated in the same order, the execution order is not guaranteed

Compatibility

Any @parametrize decorator can be converted to @pytest.mark.parametrize just by changing its name. @pytest.mark.parametrize decorator can be converted to @parametrize as long as pytest.param, indirect, ids and scope are not used.

@parametrize works with both unittest and pytest. However, pytest is recommended due to limitations when using unittest in cli.

Parametrized tests are properly detected and handled by PyCharm. They are displayed as if they were parametrized with @pytest.mark.parametrize.

Limitations

Since @parametrize does some kind of magic under the hood, there are some limitations you need to consider. It's likely you will never face most of them, but if you will, @parametrize will let you know with an error:

  • All parametrization must be done via decorators

    :white_check_mark: OK

    @parametrize('a', (1, 2))
    def f(a):
        ...
    

    :x: Won't work:

    def f(a):
        ...
    
    parametrize('a', (1, 2))(func)
    
    RuntimeError: Unable to find any parametrizes in decorators, please rewrite decorator name to match any of detected names @{'parametrize'}
    
  • All other decorators must be defined before parametrize decorators

    :white_check_mark: OK:

    @parametrize("a", (1, 2))
    @parametrize("b", (2, 3))
    @mock.patch(f"{__name__}.bar", "foo")
    def f(a, b):
        return a, b
    

    :x: Won't work:

    @mock.patch(f"{__name__}.bar", "foo")
    @parametrize("a", (1, 2))
    @parametrize("b", (2, 3))
    def f(a, b):
        return a, b
    
    TypeError: @mock.patch(f"{__name__}.bar", "foo") must be defined before any of @{'parametrize'} decorators
    
  • If you assign parametrized decorator to variable, it must be accessible from locals() or globals() namespaces:

    :white_check_mark: OK:

    a_parameters = parametrize("a", (4, 5))  # defined in module
    def func():  
        class TestSomething:
            b_parameters = parametrize("b", (1, 2, 3))
    
            @b_parameters  # b_parameters found in locals()
            @a_parameters  # a_parameters found in globals()
            def test_method(self, a, b):
                ...
    

    :x: Won't work:

    def func():
        # defined in function scope
        a_parameters = parametrize("a", (4, 5))
    
        class TestSomething:
            print('a_parameters' in {**globals(), **locals()})  # False
    
            @a_parameters  # accessed in class body scope
            def test_method(self, a, b):
                ...
    
    RuntimeError: Unable to find any parametrizes in decorators, please rewrite decorator name to match any of detected names @{'parametrize'}  
    
  • Parametrized method can be ran from command line only via pytest:

    $ cat test.py

    import unittest
    from parametrize import parametrize
    
    class TestSomething(unittest.TestCase):
        @parametrize('a', (1, 2))
        def test_something(self, a):
            self.assertIsInstance(a, int)
    

    :white_check_mark: OK:

    $ pytest test.py::TestSomething::test_something -v

     ...    
     test.py::TestSomething.test_something[1]                                        50% █████     
     test.py::TestSomething.test_something[2]                                       100% ██████████
    
    Results (0.07s):
           2 passed
    

    :x: Won't work:

    $ python -m unittest test.TestSomething.test_something

    Traceback (most recent call last):
      ...
    TypeError: don't know how to make test from: test_something[...]
    
  • @parametrize cannot be used in interactive environments like REPL (It works in IPython though)

  • @parametrize cannot be used in cythonized code

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

parametrize-0.1.1.tar.gz (7.7 kB view details)

Uploaded Source

Built Distribution

parametrize-0.1.1-py3-none-any.whl (7.4 kB view details)

Uploaded Python 3

File details

Details for the file parametrize-0.1.1.tar.gz.

File metadata

  • Download URL: parametrize-0.1.1.tar.gz
  • Upload date:
  • Size: 7.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.6 CPython/3.9.5 Linux/5.4.0-1046-azure

File hashes

Hashes for parametrize-0.1.1.tar.gz
Algorithm Hash digest
SHA256 d7ac0f61b781d1eadfa81d9e57ea80d5e184078e1976b7bb052ab682d9ef35de
MD5 06d3719f9d86458dbeb1575c871d890d
BLAKE2b-256 3b18983fbff2728e2b23913279b7218829d43565b6e90c88363a4e76ad2456ee

See more details on using hashes here.

File details

Details for the file parametrize-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: parametrize-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 7.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.6 CPython/3.9.5 Linux/5.4.0-1046-azure

File hashes

Hashes for parametrize-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 618fc00d15a03df7177691e83e59aeb976b20c410ce39af5063d1839a4673645
MD5 498c3c75b9a681a41fd66c7c2dfc47a6
BLAKE2b-256 ca2c0258127958773f87feaee47870627080a3e59cb8c691fcefe9fa10334114

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page