Skip to content

Order of class fixtures #484

Closed
Closed

Description

@pytestbot

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?


Activity

  1. pytestbot commented on Mar 18, 2014

    @pytestbot
    ContributorAuthor

    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ć

  2. pytestbot commented on Mar 18, 2014

    @pytestbot
    ContributorAuthor

    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.

  3. pytestbot commented on Mar 19, 2014

    @pytestbot
    ContributorAuthor

    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ć

  4. pytestbot commented on Mar 19, 2014

    @pytestbot
    ContributorAuthor

    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.

  5. pytestbot commented on Mar 19, 2014

    @pytestbot
    ContributorAuthor

    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ć

  6. pytestbot commented on Mar 19, 2014

    @pytestbot
    ContributorAuthor

    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 :-)

  7. pytestbot commented on Mar 24, 2014

    @pytestbot
    ContributorAuthor

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


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

  8. added
    type: proposalproposal for a new feature, often to gather opinions or design the API around the new feature
    on Jun 15, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: proposalproposal for a new feature, often to gather opinions or design the API around the new feature

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions