Skip to content

Commit 8a945b9

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 b29ae03 commit 8a945b9

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
@@ -267,6 +267,7 @@ Vidar T. Fauske
267267
Virgil Dupras
268268
Vitaly Lashmanov
269269
Vlad Dragos
270+
Vladyslav Rachek
270271
Volodymyr Piskun
271272
Wei Lin
272273
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
@@ -998,6 +998,7 @@ def parametrize(
998998
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
999999

10001000
self._validate_if_using_arg_names(argnames, indirect)
1001+
self._validate_explicit_parameters(argnames, indirect)
10011002

10021003
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
10031004

@@ -1151,6 +1152,34 @@ def _validate_if_using_arg_names(self, argnames, indirect):
11511152
pytrace=False,
11521153
)
11531154

1155+
def _validate_explicit_parameters(self, argnames, indirect):
1156+
"""
1157+
The argnames in *parametrize* should either be declared explicitly via
1158+
indirect list or explicitly in the function
1159+
1160+
:param List[str] argnames: list of argument names passed to ``parametrize()``.
1161+
:param indirect: same ``indirect`` parameter of ``parametrize()``.
1162+
:raise ValueError: if validation fails
1163+
"""
1164+
func_name = self.function.__name__
1165+
if type(indirect) is bool and indirect is True:
1166+
return
1167+
parametrized_argnames = list()
1168+
funcargnames = _pytest.compat.getfuncargnames(self.function)
1169+
if type(indirect) is list:
1170+
for arg in argnames:
1171+
if arg not in indirect:
1172+
parametrized_argnames.append(arg)
1173+
elif indirect is False:
1174+
parametrized_argnames = argnames
1175+
for arg in parametrized_argnames:
1176+
if arg not in funcargnames:
1177+
raise ValueError(
1178+
f'In function "{func_name}":\n'
1179+
f'Parameter "{arg}" should be declared explicitly via indirect\n'
1180+
f"or in function itself"
1181+
)
1182+
11541183

11551184
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
11561185
"""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
@@ -1877,3 +1877,53 @@ def test_converted_to_str(a, b):
18771877
"*= 6 passed in *",
18781878
]
18791879
)
1880+
1881+
def test_parametrize_explicit_parameters_func(self, testdir):
1882+
testdir.makepyfile(
1883+
"""
1884+
import pytest
1885+
1886+
1887+
@pytest.fixture
1888+
def fixture(arg):
1889+
return arg
1890+
1891+
@pytest.mark.parametrize("arg", ["baz"])
1892+
def test_without_arg(fixture):
1893+
assert "baz" == fixture
1894+
"""
1895+
)
1896+
result = testdir.runpytest()
1897+
result.assert_outcomes(error=1)
1898+
result.stdout.fnmatch_lines(
1899+
[
1900+
'*In function "test_without_arg"*',
1901+
'*Parameter "arg" should be declared explicitly via indirect*',
1902+
"*or in function itself*",
1903+
]
1904+
)
1905+
1906+
def test_parametrize_explicit_parameters_method(self, testdir):
1907+
testdir.makepyfile(
1908+
"""
1909+
import pytest
1910+
1911+
class Test:
1912+
@pytest.fixture
1913+
def test_fixture(self, argument):
1914+
return argument
1915+
1916+
@pytest.mark.parametrize("argument", ["foobar"])
1917+
def test_without_argument(self, test_fixture):
1918+
assert "foobar" == test_fixture
1919+
"""
1920+
)
1921+
result = testdir.runpytest()
1922+
result.assert_outcomes(error=1)
1923+
result.stdout.fnmatch_lines(
1924+
[
1925+
'*In function "test_without_argument"*',
1926+
'*Parameter "argument" should be declared explicitly via indirect*',
1927+
"*or in function itself*",
1928+
]
1929+
)

0 commit comments

Comments
 (0)