Skip to content

Commit

Permalink
Force explicit declaration of args in parametrize
Browse files Browse the repository at this point in the history
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?
  • Loading branch information
erheron committed Dec 12, 2019
1 parent b29ae03 commit 8a945b9
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 0 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -267,6 +267,7 @@ Vidar T. Fauske
Virgil Dupras
Vitaly Lashmanov
Vlad Dragos
Vladyslav Rachek
Volodymyr Piskun
Wei Lin
Wil Cooley
Expand Down
3 changes: 3 additions & 0 deletions doc/en/example/parametrize.rst
Expand Up @@ -398,6 +398,9 @@ The result of this test will be successful:
.. regendoc:wipe
Note, that each argument in `parametrize` list should be explicitly declared in corresponding
python test function or via `indirect`.

Parametrizing test methods through per-class configuration
--------------------------------------------------------------

Expand Down
29 changes: 29 additions & 0 deletions src/_pytest/python.py
Expand Up @@ -998,6 +998,7 @@ def parametrize(
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)

self._validate_if_using_arg_names(argnames, indirect)
self._validate_explicit_parameters(argnames, indirect)

arg_values_types = self._resolve_arg_value_types(argnames, indirect)

Expand Down Expand Up @@ -1151,6 +1152,34 @@ def _validate_if_using_arg_names(self, argnames, indirect):
pytrace=False,
)

def _validate_explicit_parameters(self, argnames, indirect):
"""
The argnames in *parametrize* should either be declared explicitly via
indirect list or explicitly in the function
:param List[str] argnames: list of argument names passed to ``parametrize()``.
:param indirect: same ``indirect`` parameter of ``parametrize()``.
:raise ValueError: if validation fails
"""
func_name = self.function.__name__
if type(indirect) is bool and indirect is True:
return
parametrized_argnames = list()
funcargnames = _pytest.compat.getfuncargnames(self.function)
if type(indirect) is list:
for arg in argnames:
if arg not in indirect:
parametrized_argnames.append(arg)
elif indirect is False:
parametrized_argnames = argnames
for arg in parametrized_argnames:
if arg not in funcargnames:
raise ValueError(
f'In function "{func_name}":\n'
f'Parameter "{arg}" should be declared explicitly via indirect\n'
f"or in function itself"
)


def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
"""Find the most appropriate scope for a parametrized call based on its arguments.
Expand Down
50 changes: 50 additions & 0 deletions testing/python/metafunc.py
Expand Up @@ -1877,3 +1877,53 @@ def test_converted_to_str(a, b):
"*= 6 passed in *",
]
)

def test_parametrize_explicit_parameters_func(self, testdir):
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def fixture(arg):
return arg
@pytest.mark.parametrize("arg", ["baz"])
def test_without_arg(fixture):
assert "baz" == fixture
"""
)
result = testdir.runpytest()
result.assert_outcomes(error=1)
result.stdout.fnmatch_lines(
[
'*In function "test_without_arg"*',
'*Parameter "arg" should be declared explicitly via indirect*',
"*or in function itself*",
]
)

def test_parametrize_explicit_parameters_method(self, testdir):
testdir.makepyfile(
"""
import pytest
class Test:
@pytest.fixture
def test_fixture(self, argument):
return argument
@pytest.mark.parametrize("argument", ["foobar"])
def test_without_argument(self, test_fixture):
assert "foobar" == test_fixture
"""
)
result = testdir.runpytest()
result.assert_outcomes(error=1)
result.stdout.fnmatch_lines(
[
'*In function "test_without_argument"*',
'*Parameter "argument" should be declared explicitly via indirect*',
"*or in function itself*",
]
)

0 comments on commit 8a945b9

Please sign in to comment.