Skip to content

Commit 5a26792

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 5a26792

File tree

4 files changed

+72
-0
lines changed

4 files changed

+72
-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: 31 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,36 @@ 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+
# reference: https://docs.python.org/3/library/inspect.html#inspect.getfullargspec
1122+
# this will contain args, vargargs, and everything python function can have
1123+
if arg not in funcargnames:
1124+
raise ValueError(
1125+
f'In function "{func_name}":\n'
1126+
f'Parameter "{arg}" should be declared explicitly via indirect\n'
1127+
f"or in function itself"
1128+
)
1129+
10991130

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

testing/python/metafunc.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,3 +1780,40 @@ 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+
1802+
def test_parametrize_explicit_parameters_method(self, testdir):
1803+
testdir.makepyfile(
1804+
"""
1805+
import pytest
1806+
1807+
class Test:
1808+
@pytest.fixture
1809+
def test_fixture(self, argument):
1810+
return argument
1811+
1812+
@pytest.mark.parametrize("argument", ["foobar"])
1813+
def test_without_argument(self, test_fixture):
1814+
assert "foobar" == test_fixture
1815+
"""
1816+
)
1817+
result = testdir.runpytest()
1818+
result.assert_outcomes(error=1)
1819+
# assert result.ret == ExitCode.INTERRUPTED # ???

0 commit comments

Comments
 (0)