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

Order of class fixtures #484

Closed
pytestbot opened this issue Mar 18, 2014 · 7 comments
Closed

Order of class fixtures #484

pytestbot opened this issue Mar 18, 2014 · 7 comments
Labels
type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature

Comments

@pytestbot
Copy link
Contributor

Originally reported by: BitBucket: javex, GitHub: javex


I have two fixtures on on a class, like this:

class TestMyThing(object):
    @pytest.fixture(autouse=True)
    def _fixture_0(self):
        self.data = ...

    @pytest.fixture(autouse=True)
    def _fixture_1(self):
        data = self.data
        ...

The problem here is that _fixture_1 needs _fixture_0 to be run before _fixture_1 as it needs self.data to be set. I'd really like to split these two up instead of using a single fixture as they are fairly complex. They are also only used on this particular class, so I'd prefer to not have it outside of my class for organizational purposes.

However, if I name the fixtures in such a way that lexicographic ordering is the other way around, they will not be called in the right order. I guess that pytest orders fixtures somehow internally (or maybe it is internal ordering of python?).

Anyways, I'd like some deterministic way of fixture ordering to be documented or configurable. One way I could imagine would be to just document that the will always be lexicographically ordered, thus you can be sure that my above naming would always be in the correct order.

Another way would be some kind of mechanism by which I can require another class fixture, since I would order other fixtures this way by declaring one requires the other.

Do you think such a feature would be possible? Or do you think it is a very special application that doesn't deserve it?


@pytestbot
Copy link
Contributor Author

Original comment by Jurko Gospodnetić (BitBucket: jurko, GitHub: jurko):


Don't know exactly how pytest handles fixtures defined inside classes (never used those 😄), but my first thought was: 'Have you tried passing _fixture_0 as a parameter to _fixture_1 and then making _fixture_0 value accessible via _fixture_1?

See pytest docs for an example on how to use one fixture from another.

But trying to rewrite your code example to do this, I really have to wander - why? Something is missing in that example. Why is _fixture_0 a fixture at all? What does its function return? Why doesn't _fixture_1 return both things?

Could you perhaps post a more complete example? Unless you are absolutely certain your situation really needs fixed fixture call ordering and nothing else will suffice - in which case someone more knowledgeable about pytest internals will have to help you. 😢

Best regards,
Jurko Gospodnetić

@pytestbot
Copy link
Contributor Author

Original comment by BitBucket: javex, GitHub: javex:


I am using something similar to autouse fixtures (xUnit setup on steroids). However, I found the params argument to be extremely useful to create a testing matrix and so I often use multiple fixtures that all have params and so they test any combination there is to test.

This combination is mainly what creates the problem. Here is a more complete example:

class TestMyThing(object):
    @pytest.fixture(autouse=True, params=[0, 1, 2])
    def _fixture_0(self, request):
        self.parent = Parent()
        self.children_count = request.param
        for i in range(request.param):
            self.parent.children.append(Child())

    @pytest.fixture(autouse=True, params=['val1', 'val2'])
    def _fixture_1(self, request):
        self.expected_val = request.param
        for child in self.parent.children:
            child.value = request.param

    def test_children(self):
        assert len(self.parent.children) == self.children_count
        for child in self.parent.children:
            assert child.value == self.expected_val

It is a bit arbitrary but I hope it gets over the message: I want to test the matrix consisting of the params of _fixture_0 (which is a count of how many children to create) and those of _fixture_1 (what value should be assigned to a specific field on those children).

As a result I get the product of those two as testing cases and on each now test_children checks that the right amount exists and each child has the expected value. Don't bother with the details of the test here, it is extremely simplified to show the case.

Now I realize that I could just move the fixtures out of the class and have a single autouse fixture that requires those two, assigns them to self and then the test is run. I also realize that I could create the product matrix myself and use a single fixture with all code.

However, the code is fairly large and it only applies to this particular test class. So moving it out of the class means polluting the global namespace which I'd like to avoid. And merging all fixtures (currently 3) into one means creating the matrix myself.

I realize that this is probably some crazy-ass stuff that nobody ever intended to use but from my perspective it would be the cleanest solution if I could do something like before='_fixture_1' or requires='_fixture_0'. I think this would be cool, but I am also open to hear any solutions I might not have considered. If this is denied (which is okay for me), then I'd probably resort to polluting my global namespace in this module.

@pytestbot
Copy link
Contributor Author

Original comment by Jurko Gospodnetić (BitBucket: jurko, GitHub: jurko):


What I referred to in my original reply was something like this:

#!python
import pytest

class Child:
    pass

class Parent:
    def __init__(self):
        self.children = []

class TestMyThing:
    @pytest.fixture(autouse=True, params=[0, 1, 2])
    def _fixture_02(self, request):
        self.parent = Parent()
        self.children_count = request.param
        for i in range(request.param):
            self.parent.children.append(Child())

    @pytest.fixture(autouse=True, params=['val1', 'val2'])
    def _fixture_01(self, request, _fixture_02):
        self.expected_val = request.param
        for child in self.parent.children:
            child.value = request.param

    def test_children(self):
        assert len(self.parent.children) == self.children_count
        for child in self.parent.children:
            assert child.value == self.expected_val

I renamed your fixtures so they get instantiated in an 'invalid' order by default, and then got them to be instantiated in the correct order by explicitly marking the second one as using the first one (i.e. by passing the first one to the second one as an argument).

Although, I do not like the approach you take here. Too much seems to be done behind the scene. If I needed such a refactoring, I'd most likely do it something like this (disclaimer: naming chosen only to match the original example):

#!python
import pytest

class Child:
    pass

class Parent:
    def __init__(self):
        self.children = []

class TestMyThing:
    def _auto_fixture_helper_02(self, param):
        self.parent = Parent()
        self.children_count = param
        for i in range(param):
            self.parent.children.append(Child())

    def _auto_fixture_helper_01(self, param):
        self.expected_val = param
        for child in self.parent.children:
            child.value = param

    @pytest.fixture(autouse=True, params=[(p02, p01)
        for p02 in (0, 1, 2)
        for p01 in ('val1', 'val2')])
    def _auto_fixture(self, request):
        param_02, param_01 = request.param
        self._auto_fixture_helper_02(param_02)
        self._auto_fixture_helper_01(param_01)

    def test_children(self):
        assert len(self.parent.children) == self.children_count
        for child in self.parent.children:
            assert child.value == self.expected_val

What I would like to see in pytest is a way to more easily define orthogonal fixture parametrizations. For example using separate decorators, the same as can be done for specific test cases using pytest.mark.parametrize:

#!python
@pytest.mark.parametrize("p1", ("one", "two", "three"))
@pytest.mark.parametrize("p2", (3, 2, 1))
def test_me(p1, p2):
  ...

That would allow you to refactor by assigning names to each of those decorators and reuse them where needed or simply use their names as something revealing your intent. But on the other hand, I don't think that would buy us much in this scenario and I do not have a more applicable scenario ready here so I guess it all just falls under the 'bikeshedding' category. 😄

Hope this helps.

Best regards,
Jurko Gospodnetić

@pytestbot
Copy link
Contributor Author

Original comment by BitBucket: javex, GitHub: javex:


Wow, I did not know about parametrize, that is actually awesome! If this would be possible on fixtures instead of just single test functions, it would solve my problem. For now, I have taken the fixtures out of the class which seems to be the cleanest solution.

@pytestbot
Copy link
Contributor Author

Original comment by Jurko Gospodnetić (BitBucket: jurko, GitHub: jurko):


As I see it, there really is not much difference (except for some syntactic sugar) between:

#!python
@some_fictional_fixture_parametrize("p1", ("one", "two", "three"))
@some_fictional_fixture_parametrize("p2", (3, 2, 1))
@pytest.fixture(autouse=True)
def _fixate_me(request):
  ... use request.param.p1 & request.param.p2 here...

and:

#!python
@pytest.fixture(autouse=True, params=[(p1, p2)
    for p1 in ("one", "two", "three")
    for p2 in (3, 2, 1)])
def _fixate_me(request):
  p1, p2 = request.param
  ... use p1 & p2 here...

So I do not really see how having the first technique available would 'solve your original problem'.

Best regards,
Jurko Gospodnetić

@pytestbot
Copy link
Contributor Author

Original comment by BitBucket: javex, GitHub: javex:


Yeah, you say syntactic sugar but if the matrix is 2x3x3x2x2 then it quickly amounts to very ugly code. But you are right, what you show there is possible. I think we can close this issue since it seems to be a corner case that you can solve with the methods you describe. Thank you for your help :-)

@pytestbot
Copy link
Contributor Author

Original comment by holger krekel (BitBucket: hpk42, GitHub: hpk42):


Thanks everyone for the discussion. And I agree with closing the issue :)

@pytestbot pytestbot added the type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature label Jun 15, 2015
fkohlgrueber pushed a commit to fkohlgrueber/pytest that referenced this issue Oct 27, 2018
The wheel package format supports including the license file. This is
done using the [metadata] section in the setup.cfg file. For additional
information on this feature, see:

https://wheel.readthedocs.io/en/stable/index.html#including-the-license-in-the-generated-wheel-file

Helps project comply with its own license:

> The above copyright notice and this permission notice shall be
> included in all copies or substantial portions of the Software.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature
Projects
None yet
Development

No branches or pull requests

1 participant