Search code examples
pythonunit-testingtestingpytestfixtures

When to use TestClass instance variables vs Pytest Fixtures


When is it appropriate to use a Pytest fixture versus an instance variable?

Suppose I have a large expected JSON output stored locally that several tests employ. Which of the following approaches is more appropriate? I can see that the state of the object remains unchanged from test to test.

@pytest.fixture
def expected_output():
    return <fixture>

def test_1(expected_output):
    <do something>
    assert output == expected_output

def test_2(expected_output):
    <do something>
    assert output == expected_output
class SomeTestClass(TestCase):
    def setUp(self):
        self.expected_output = <fixture>

    def test_1(self):
        <do something>
        self.assertEqual(output, self.expected_output)

    def test_2(self):
        <do something>
        self.assertEqual(output, self.expected_output)

I've combed through the Pytest docs and don't seem to understand the distinction. Having instance variables seems to carry a lot of benefits of fixtures--i.e., sharing values across tests.


Solution

  • The first difference you have already shown in your example: a fixture does not need a test class.

    If you use a test class, you can also use the setup method in pytest, no need to use unittest.TestCase:

    class SomeTestClass:
        def setup_method(self):
            self.expected_output = <fixture>
    
        def test_1(self):
            <do something>
            assert output == self.expected_output
    

    This is described as the classic xunit-style setup in pytest. From that documentation:

    While these setup/teardown methods are simple and familiar to those coming from a unittest or nose background, you may also consider using pytest’s more powerful fixture mechanism which leverages the concept of dependency injection, allowing for a more modular and more scalable approach for managing test state, especially for larger projects and for functional testing. You can mix both fixture mechanisms in the same file but test methods of unittest.TestCase subclasses cannot receive fixture arguments.

    Generally I would advice against mixing unittest and pytest for new test code. There is nothing in unittest that cannot be done with pytest, but you cannot run unittest against a test that uses pytest features.

    In your concrete example, you actually don't want an instance variable, because the expected output does not change with the test. You could just declare the variable statically and use it (both in or outside of classes), or you could use a class variable (and use setup_class to set it in pytest).

    For this trivial example it probably makes no real difference which concept to use, but if you you need a setup and a teardown, fixtures can be more helpfull, as they provide both:

    @pytest.fixture
    def some_resource():
        resource = acquire_resource()
        yield resource
        free(resource)
    

    In the end, there is no "appropriate" choice in the cases where you can use both variants, but using fixtures always feels more consistent - one mechanism instead of two different ones - and it is the pytest way. Not the least factor - most people using pytest use fixtures in these cases, so finding help and sharing code is easier if you use the same.