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

mark.usefixtures registered fixtures of all test class subclasses invoked despite -k #2806

Closed
rmfitzpatrick opened this issue Sep 29, 2017 · 8 comments
Labels
topic: fixtures anything involving fixtures directly or indirectly topic: marks related to marks, either the general marks or builtin topic: selection related to test selection from the command line type: bug problem that needs to be addressed

Comments

@rmfitzpatrick
Copy link

rmfitzpatrick commented Sep 29, 2017

If multiple test classes all subclass a class with shared tests and use the pytest.mark.usefixtures(), runs using -k SingleClass will still invoke excluded test class sourced fixtures.

reproduce.py

import pytest


@pytest.fixture
def one():
    print 'In one!'


@pytest.fixture
def two():
    print 'In two!'


@pytest.fixture
def three():
    print 'In three!'


class Base(object):

    def test_shared(self):
        assert 1


@pytest.mark.usefixtures('one')
class TestOne(Base):

    def test_one(self):
        assert 1


@pytest.mark.usefixtures('two')
class TestTwo(Base):

    def test_two(self):
        assert 1


@pytest.mark.usefixtures('three')
class TestThree(Base):

    def test_three(self):
        assert 1
OSX:tmp me$ pytest -vs reproduce.py -k Three
======================================================================== test session starts =========================================================================
platform darwin -- Python 2.7.13, pytest-3.2.2, py-1.4.34, pluggy-0.4.0 -- /Users/me/.virtualenvs/venv/bin/python2.7
cachedir: .cache
metadata: {'Python': '2.7.13', 'Platform': 'Darwin-15.5.0-x86_64-i386-64bit', 'Packages': {'py': '1.4.34', 'pytest': '3.2.2', 'pluggy': '0.4.0'}, 'Plugins': {'pylama': '7.4.1', 'cov': '2.5.1', 'ordering': '0.5', 'html': '1.15.2', 'catchlog': '1.2.2', 'instafail': '0.3.0', 'metadata': '1.5.0'}}
rootdir: /Users/me/tmp, inifile:
plugins: ordering-0.5, metadata-1.5.0, instafail-0.3.0, html-1.15.2, cov-2.5.1, catchlog-1.2.2, pylama-7.4.1

reproduce.py::TestThree::test_shared In one!
In two!
In three!
PASSED
reproduce.py::TestThree::test_three In three!
PASSED
@nicoddemus
Copy link
Member

Hi @rmfitzpatrick,

You expected this output:

reproduce.py::TestThree::test_shared In three!
PASSED
reproduce.py::TestThree::test_three In three!
PASSED

Correct? (Btw, in the future try to add your expectation explicitly, it helps us maintainers sometimes 😉)

This is a long term problem with our marks system, marks are attached to the python object that decorates them and accumulated there, instead of attached to the collection item the python object represents.

For example, when you decorate a test_foo function with a mark, the MarkInfo object is attached to the test_foo function. The same happens to classes, in which case this is more problematic because there's inheritance involved.

Fixtures, on the other hand, are attached to the collection item where they were declared. For example, a fixture declared in a module will be attached to the Module item created by pytest to represent that module, not to the python module object itself.

@RonnyPfannschmidt is bravely undergoing a crusade in order to fix this wart in our code-base.

For know there's no known solution to this problem, other than stop using usefixtures marker and use this ugly workaround instead:

class TestOne(Base):

    @pytest.fixture(autouse=True)
    def __foo(self, one):
        pass

class TestTwo(Base):
    @pytest.fixture(autouse=True)
    def __foo(self, two):
        pass

class TestThree(Base):
    @pytest.fixture(autouse=True)
    def __foo(self, three):
        pass

This forces your classes to instantiate the appropriate fixtures using the fixtures mechanism itself, and not marks. With this I can obtain the desired output:

collected 6 items

.tmp/test_usefixtures.py::TestThree::test_shared In three!
PASSED
.tmp/test_usefixtures.py::TestThree::test_three In three!
PASSED

============================= 4 tests deselected ==============================

@nicoddemus nicoddemus added topic: marks related to marks, either the general marks or builtin type: bug problem that needs to be addressed labels Sep 29, 2017
@rmfitzpatrick
Copy link
Author

rmfitzpatrick commented Sep 29, 2017

Thanks @nicoddemus for the suggested workaround, though I'd rather not have to define fixtures for each class over using a single definition. (edit: the functionality of this just became clear to me)

You are correct that I'd only expect

.tmp/test_usefixtures.py::TestThree::test_shared In three!
PASSED
.tmp/test_usefixtures.py::TestThree::test_three In three!
PASSED

@nicoddemus
Copy link
Member

You can use a decorator to inject the autouse fixture for you:

def use_fixtures_workaround(*names):

    def inner(cls):

        @pytest.fixture(autouse=True)
        def __usefixtures_workaround(self, request):
            for name in names:
                request.getfixturevalue(name)
        cls.__usefixtures_workaround = __usefixtures_workaround
        return cls

    return inner

@use_fixtures_workaround('one')
class TestOne(Base):

    def test_one(self):
        assert 1

@use_fixtures_workaround('two')
class TestTwo(Base):

    def test_two(self):
        assert 1

@use_fixtures_workaround('three')
class TestThree(Base):

    def test_three(self):
        assert 1

@obestwalter
Copy link
Member

obestwalter commented Sep 29, 2017

Wow @nicoddemus ... that's some quite scary advanced pytest foo 🥇

@nicoddemus
Copy link
Member

Hehehe!

At its heart, it is just a class decorator which injects a function, which happens to be decorated with @pytest.fixture(autouse=True) 😉

@obestwalter
Copy link
Member

Einstein must have said something similar about his theory of general relativity 😁

@RonnyPfannschmidt
Copy link
Member

#535 and #568 are the culprit

@Zac-HD Zac-HD added topic: selection related to test selection from the command line topic: fixtures anything involving fixtures directly or indirectly labels Oct 21, 2018
@Zac-HD
Copy link
Member

Zac-HD commented Oct 21, 2018

Closed by #3317.

@Zac-HD Zac-HD closed this as completed Oct 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: fixtures anything involving fixtures directly or indirectly topic: marks related to marks, either the general marks or builtin topic: selection related to test selection from the command line type: bug problem that needs to be addressed
Projects
None yet
Development

No branches or pull requests

5 participants