Skip to content

Commit b6f2db9

Browse files
committed
Force explicit declaration of args in parametrize
Every argname used in `parametrize` either must be declared explicitly in the python test function, or via `indirect` list References #5712 TODO: as of now, ValueError occurs during collection phase. Maybe we want it to appear during other phase?
1 parent 8cdf9d4 commit b6f2db9

File tree

4 files changed

+83
-0
lines changed

4 files changed

+83
-0
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ Vidar T. Fauske
265265
Virgil Dupras
266266
Vitaly Lashmanov
267267
Vlad Dragos
268+
Vladyslav Rachek
268269
Volodymyr Piskun
269270
Wei Lin
270271
Wil Cooley

doc/en/example/parametrize.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ The result of this test will be successful:
398398
399399
.. regendoc:wipe
400400
401+
Note, that each argument in `parametrize` list should be explicitly declared in corresponding
402+
python test function or via `indirect`.
403+
401404
Parametrizing test methods through per-class configuration
402405
--------------------------------------------------------------
403406

src/_pytest/python.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None)
974974
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
975975

976976
self._validate_if_using_arg_names(argnames, indirect)
977+
self._validate_explicit_parameters(argnames, indirect)
977978

978979
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
979980

@@ -1096,6 +1097,34 @@ def _validate_if_using_arg_names(self, argnames, indirect):
10961097
pytrace=False,
10971098
)
10981099

1100+
def _validate_explicit_parameters(self, argnames, indirect):
1101+
"""
1102+
The argnames in *parametrize* should either be declared explicitly via
1103+
indirect list or explicitly in the function
1104+
1105+
:param List[str] argnames: list of argument names passed to ``parametrize()``.
1106+
:param indirect: same ``indirect`` parameter of ``parametrize()``.
1107+
:raise ValueError: if validation fails
1108+
"""
1109+
func_name = self.function.__name__
1110+
if type(indirect) is bool and indirect is True:
1111+
return
1112+
parametrized_argnames = list()
1113+
funcargnames = _pytest.compat.getfuncargnames(self.function)
1114+
if type(indirect) is list:
1115+
for arg in argnames:
1116+
if arg not in indirect:
1117+
parametrized_argnames.append(arg)
1118+
elif indirect is False:
1119+
parametrized_argnames = argnames
1120+
for arg in parametrized_argnames:
1121+
if arg not in funcargnames:
1122+
raise ValueError(
1123+
f'In function "{func_name}":\n'
1124+
f'Parameter "{arg}" should be declared explicitly via indirect\n'
1125+
f"or in function itself"
1126+
)
1127+
10991128

11001129
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
11011130
"""Find the most appropriate scope for a parametrized call based on its arguments.

testing/python/metafunc.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,3 +1780,53 @@ def test_foo(a):
17801780
)
17811781
result = testdir.runpytest()
17821782
result.assert_outcomes(passed=1)
1783+
1784+
def test_parametrize_explicit_parameters_func(self, testdir):
1785+
testdir.makepyfile(
1786+
"""
1787+
import pytest
1788+
1789+
1790+
@pytest.fixture
1791+
def fixture(arg):
1792+
return arg
1793+
1794+
@pytest.mark.parametrize("arg", ["baz"])
1795+
def test_without_arg(fixture):
1796+
assert "baz" == fixture
1797+
"""
1798+
)
1799+
result = testdir.runpytest()
1800+
result.assert_outcomes(error=1)
1801+
result.stdout.fnmatch_lines(
1802+
[
1803+
'*In function "test_without_arg"*',
1804+
'*Parameter "arg" should be declared explicitly via indirect*',
1805+
"*or in function itself*",
1806+
]
1807+
)
1808+
1809+
def test_parametrize_explicit_parameters_method(self, testdir):
1810+
testdir.makepyfile(
1811+
"""
1812+
import pytest
1813+
1814+
class Test:
1815+
@pytest.fixture
1816+
def test_fixture(self, argument):
1817+
return argument
1818+
1819+
@pytest.mark.parametrize("argument", ["foobar"])
1820+
def test_without_argument(self, test_fixture):
1821+
assert "foobar" == test_fixture
1822+
"""
1823+
)
1824+
result = testdir.runpytest()
1825+
result.assert_outcomes(error=1)
1826+
result.stdout.fnmatch_lines(
1827+
[
1828+
'*In function "test_without_argument"*',
1829+
'*Parameter "argument" should be declared explicitly via indirect*',
1830+
"*or in function itself*",
1831+
]
1832+
)

0 commit comments

Comments
 (0)