From 957712059206ea0994780719b98f38bc9e3dfcd4 Mon Sep 17 00:00:00 2001 From: Mike Lundy Date: Tue, 8 Mar 2016 18:16:57 -0800 Subject: [PATCH] Allow custom fixture names for fixtures When defining a fixture in the same module as where it is used, the function argument shadows the fixture name, which a) annoys pylint and b) can lead to bugs where you forget to request a fixture into a test method. This allows one to define fixtures with a different name than the name of the function, bypassing that problem. --- AUTHORS | 1 + CHANGELOG.rst | 8 +++++++- _pytest/python.py | 18 ++++++++++++++---- testing/python/fixture.py | 11 +++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 47f137892..a6021c311 100644 --- a/AUTHORS +++ b/AUTHORS @@ -63,6 +63,7 @@ Matt Williams Michael Aquilina Michael Birtwell Michael Droettboom +Mike Lundy Nicolas Delaby Pieter Mulder Piotr Banaszkiewicz diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 34fc1916d..0e7107337 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,7 +7,11 @@ namespace in which your doctests run. Thanks `@milliams`_ for the complete PR (`#1428`_). -* +* New ``name`` argument to ``pytest.fixture`` mark, which allows a custom name + for a fixture (to solve the funcarg-shadowing-fixture problem). + Thanks `@novas0x2a`_ for the complete PR (`#1444`_). + +* * @@ -21,8 +25,10 @@ * .. _@milliams: https://github.com/milliams +.. _@novas0x2a: https://github.com/novas0x2a .. _#1428: https://github.com/pytest-dev/pytest/pull/1428 +.. _#1444: https://github.com/pytest-dev/pytest/pull/1444 2.9.1.dev1 diff --git a/_pytest/python.py b/_pytest/python.py index ec346f587..1d1d9e158 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -114,12 +114,13 @@ def safe_getattr(object, name, default): class FixtureFunctionMarker: def __init__(self, scope, params, - autouse=False, yieldctx=False, ids=None): + autouse=False, yieldctx=False, ids=None, name=None): self.scope = scope self.params = params self.autouse = autouse self.yieldctx = yieldctx self.ids = ids + self.name = name def __call__(self, function): if isclass(function): @@ -129,7 +130,7 @@ class FixtureFunctionMarker: return function -def fixture(scope="function", params=None, autouse=False, ids=None): +def fixture(scope="function", params=None, autouse=False, ids=None, name=None): """ (return a) decorator to mark a fixture factory function. This decorator can be used (with or or without parameters) to define @@ -155,14 +156,21 @@ def fixture(scope="function", params=None, autouse=False, ids=None): so that they are part of the test id. If no ids are provided they will be generated automatically from the params. + :arg name: the name of the fixture. This defaults to the name of the + decorated function. If a fixture is used in the same module in + which it is defined, the function name of the fixture will be + shadowed by the function arg that requests the fixture; one way + to resolve this is to name the decorated function + ``fixture_`` and then use + ``@pytest.fixture(name='')``. """ if callable(scope) and params is None and autouse == False: # direct decoration return FixtureFunctionMarker( - "function", params, autouse)(scope) + "function", params, autouse, name=name)(scope) if params is not None and not isinstance(params, (list, tuple)): params = list(params) - return FixtureFunctionMarker(scope, params, autouse, ids=ids) + return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name) def yield_fixture(scope="function", params=None, autouse=False, ids=None): """ (return a) decorator to mark a yield-fixture factory function @@ -1989,6 +1997,8 @@ class FixtureManager: # fixture attribute continue else: + if marker.name: + name = marker.name assert not name.startswith(self._argprefix) fixturedef = FixtureDef(self, nodeid, name, obj, marker.scope, marker.params, diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 506d8426e..eb8f9f34b 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -2691,3 +2691,14 @@ class TestContextManagerFixtureFuncs: *def arg1* """) + def test_custom_name(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(name='meow') + def arg1(): + return 'mew' + def test_1(meow): + print(meow) + """) + result = testdir.runpytest("-s") + result.stdout.fnmatch_lines("*mew*")