Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues a warning when a function is not collected as a test case just because it uses @pytest.fixture (#12989) #13051

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/12989.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Issues a warning when a function is not collected as a test case just because it uses :py:func:`pytest.fixture`.
This helps beginners distinguish fixtures from tests; experienced users can ignore the warning via config.

.. code-block:: ini

[pytest]
filterwarnings =
ignore:.*becomes a fixture.*:pytest.PytestCollectionWarning
59 changes: 59 additions & 0 deletions doc/en/explanation/fixtures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,65 @@ prefer. You can also start out from existing :ref:`unittest.TestCase
style <unittest.TestCase>`.


Distinguishing fixtures and tests
------------------------------------

Fixtures and tests may seem similar: both are functions (or methods) and both can utilize fixtures.
However, there is a fundamental difference:

- **Tests** are the leading role.
They can actively use :ref:`mark <mark>` and fixtures.

- **Fixtures** are supporting role.
They cannot use mark or tests and only be used directly or indirectly by test cases.


Two classic traps:


- Will not fail, because adding any tags to the fixture is invalid

.. code-block:: python

import warnings


@pytest.fixture
@pytest.mark.filterwarnings("error") # fixture cannot use mark
def server():
print("fixture is running!")


def test_foo(server):
warnings.warn(UserWarning("api v1, should use functions from v2"))



- No code will execute, because the ``test_foo`` becomes a fixture after using ``@pytest.fixture``


.. code-block:: python

@pytest.fixture
def server():
print("fixture is running!")


@pytest.fixture # turn test case into fixtures
def test_foo(server):
warnings.warn(UserWarning("api v1, should use functions from v2"))



.. versionadded:: 8.0

Applying a mark to a fixture function now issues a warning and will become an error in pytest 9.0.


.. versionadded:: 8.4

Issues a warning when a function is not collected as a test case just because it uses ``@pytest.fixture``


Fixture errors
--------------
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ filterwarnings = [
"ignore:VendorImporter\\.find_spec\\(\\) not found; falling back to find_module\\(\\):ImportWarning",
# https://github.com/pytest-dev/execnet/pull/127
"ignore:isSet\\(\\) is deprecated, use is_set\\(\\) instead:DeprecationWarning",
"ignore:.*becomes a fixture.*:pytest.PytestCollectionWarning",
]
pytester_example_dir = "testing/example_scripts"
markers = [
Expand Down
10 changes: 10 additions & 0 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,16 @@

def istestfunction(self, obj: object, name: str) -> bool:
if self.funcnamefilter(name) or self.isnosetest(obj):
if isinstance(obj, fixtures.FixtureFunctionDefinition):
self.warn(

Check warning on line 358 in src/_pytest/python.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/python.py#L358

Added line #L358 was not covered by tests
PytestCollectionWarning(
f"cannot collect test function {name!r},"
f"because it used the '@pytest.fixture' than becomes a fixture "
f"(from: {self.nodeid})"
)
)
return False

Check warning on line 365 in src/_pytest/python.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/python.py#L365

Added line #L365 was not covered by tests

if isinstance(obj, (staticmethod, classmethod)):
# staticmethods and classmethods need to be unwrapped.
obj = safe_getattr(obj, "__func__", False)
Expand Down
20 changes: 20 additions & 0 deletions testing/python/collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,26 @@ def test_function_with_square_brackets(self, pytester: Pytester) -> None:
]
)

@pytest.mark.filterwarnings("default::UserWarning")
def test_function_used_fixture(self, pytester: Pytester):
pytester.makeini("[pytest]")
pytester.makepyfile(
"""
import pytest
@pytest.fixture
def test_function():
pass
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"collected 0 items",
"*== warnings summary ==*",
"*because it used the '@pytest.fixture' than becomes a fixture*",
]
)


class TestSorting:
def test_check_equality(self, pytester: Pytester) -> None:
Expand Down
Loading