https://github.com/bobronium/parametrize
Drop-in @pytest.mark.parametrize replacement working with unittest.TestCase
https://github.com/bobronium/parametrize
Last synced: about 1 month ago
JSON representation
Drop-in @pytest.mark.parametrize replacement working with unittest.TestCase
- Host: GitHub
- URL: https://github.com/bobronium/parametrize
- Owner: Bobronium
- License: mit
- Created: 2021-05-03T12:39:14.000Z (about 4 years ago)
- Default Branch: master
- Last Pushed: 2024-12-19T16:51:43.000Z (6 months ago)
- Last Synced: 2025-04-22T00:38:42.676Z (about 2 months ago)
- Language: Python
- Size: 62.5 KB
- Stars: 18
- Watchers: 1
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: readme.md
- Changelog: changelog.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/Bobronium/parametrize/actions?query=event%3Apush+branch%3Amaster+workflow%3ACI)
[](https://pypi.python.org/pypi/parametrize)
[](https://github.com/Bobronium/parametrize)
[](https://github.com/psf/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](https://docs.pytest.org/en/6.2.x/parametrize.html) adapted to unittest
```python
import unittest
from parametrize import parametrizeclass TestSomething(unittest.TestCase):
@parametrize('test_input,expected', [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(self, test_input, expected):
self.assertEqual(expected, eval(test_input))
```
```py
$ 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.001sFAILED (failures=1)
```
##### You don't need to use additional decorators, custom base classes or metaclasses.### Stacking parametrize decorators:
```python
import unittest
from parametrize import parametrizeclass 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
$ 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.000sOK
```
##### 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-method-can-be-ran-from-command-line-only-via-pytest).
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
```python
@parametrize('a', (1, 2))
def f(a):
...
```
:x: Won't work:
```python
def f(a):
...
parametrize('a', (1, 2))(f)
```
```py
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:
```py
@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:
```python
@mock.patch(f"{__name__}.bar", "foo")
@parametrize("a", (1, 2))
@parametrize("b", (2, 3))
def f(a, b):
return a, b
```
```py
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:
```py
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:
```py
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):
...
```
```py
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`
```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`
```py
...
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`
```py
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