pytest-funparam
makes it easy to write parametrized tests.
Unlike pytest.mark.parametrize
, pytest-funparam
:
- includes the failing parameter in pytest tracebacks;
- enables static type checking of parameters; and
- keeps parameters and assertions closer together.
You can install "pytest-funparam" via pip from PyPI:
$ pip install pytest-funparam
Inside a test function, decorate a function with the funparam
fixture:
def test_addition(funparam):
@funparam
def verify_sum(a, b, expected):
assert a + b == expected
verify_sum(1, 2, 3)
verify_sum(2, 2, 5) # OOPS!
verify_sum(4, 2, 6)
And run pytest:
$ pytest ============================= test session starts ============================== collected 3 items test_readme.py .F. [100%] =================================== FAILURES =================================== _______________________________ test_addition[1] _______________________________ def test_addition(funparam): @funparam def verify_sum(a, b, expected): assert a + b == expected verify_sum(1, 2, 3) > verify_sum(2, 2, 5) # OOPS! test_readme.py:7: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ a = 2, b = 2, expected = 5 @funparam def verify_sum(a, b, expected): > assert a + b == expected E assert (2 + 2) == 5 test_readme.py:4: AssertionError ========================= 1 failed, 2 passed in 0.03s ==========================
The test_addition
test case was split into 3 tests, one for each
verify_sum
call.
Because funparam
is parametrizing the test calls, it even works with
commands like pytest --last-failed
:
$ pytest --last-failed ============================= test session starts ============================== collected 1 item test_readme.py F [100%] =================================== FAILURES =================================== _______________________________ test_addition[1] _______________________________ def test_addition(funparam): @funparam def verify_sum(a, b, expected): assert a + b == expected verify_sum(1, 2, 3) > verify_sum(2, 2, 5) # OOPS! test_readme.py:7: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ a = 2, b = 2, expected = 5 @funparam def verify_sum(a, b, expected): > assert a + b == expected E assert (2 + 2) == 5 test_readme.py:4: AssertionError ============================== 1 failed in 0.01s ===============================
Mark tests by using the .marks()
method of your funparam function.
import pytest
def test_addition(funparam):
@funparam
def verify_sum(a, b, expected):
assert a + b == expected
verify_sum(1, 2, 3)
verify_sum.marks(pytest.mark.skip)(2, 2, 5)
verify_sum(4, 2, 6)
$ pytest ============================= test session starts ============================== collected 3 items test_readme.py .s. [100%] ========================= 2 passed, 1 skipped in 0.01s =========================
Similarly, add an id
to a test using the .id()
method of your funparam
function:
def test_addition(funparam):
@funparam
def verify_sum(a, b, expected):
assert a + b == expected
verify_sum.id("one and two")(1, 2, 3)
verify_sum.id("two and two")(2, 2, 5)
verify_sum.id("four and two")(4, 2, 6)
$ pytest --collect-only ============================= test session starts ============================== collected 3 items <Module test_readme.py> <Function test_addition[one and two]> <Function test_addition[two and two]> <Function test_addition[four and two]> ========================== 3 tests collected in 0.01s ==========================
You can also use the shorthand for assigning an id
. (It does the same thing
as calling .id()
.)
def test_addition(funparam):
@funparam
def verify_sum(a, b, expected):
assert a + b == expected
verify_sum["one and two"](1, 2, 3)
verify_sum["two and two"](2, 2, 5)
verify_sum["four and two"](4, 2, 6)
$ pytest --collect-only ============================= test session starts ============================== collected 3 items <Module test_readme.py> <Function test_addition[one and two]> <Function test_addition[two and two]> <Function test_addition[four and two]> ========================== 3 tests collected in 0.01s ==========================
pytest-funparam
has full type annotations. The funparam
fixture returns
a FunparamFixture
object. You can import it from pytest_funparam
:
import pytest
from pytest_funparam import FunparamFixture
def test_addition(funparam: FunparamFixture):
@funparam
def verify_sum(a: int, b: int , expected: int):
assert a + b == expected
# These are valid
verify_sum(1, 2, 3)
verify_sum['it accommodates ids'](2, 2, 4)
# Marks work too!
verify_sum.marks(pytest.mark.xfail)(2, 2, 9)
# This will be marked as invalid (since it's not an int)
verify_sum(1, '2', 3)
# Using id/marks will still preserve the function's typing.
verify_sum['should be an int'](1, 2, '3')
$ mypy test_readme.py:17: error: Argument 2 to "verify_sum" has incompatible type "str"; expected "int" test_readme.py:20: error: Argument 3 to "verify_sum" has incompatible type "str"; expected "int" Found 2 errors in 1 file (checked 1 source file)
Distributed under the terms of the MIT license, "pytest-funparam" is free and open source software
If you encounter any problems, please file an issue along with a detailed description.