Suppose I have a class something like this:
class Class:
def __init__(self, data):
self.tab1 = data.A
self.tab2 = data.B
# Other methods which try to change the state of object
def add1(self, num):
self.tab1 += num
def mul2(self, num):
self.tab2 *= num
Using pytest, I'm trying to test the state of object of Class
(say obj
) when several operations are done. For each operation I want a different test function but all of them should manipulate the same object. Since pytest doesn't allow using __init__
for test classes, only options I have is to create obj
as class variable or return it from a class-scoped fixture as mentioned in this SO thread. But I can't make obj
class variable because it needs class_data
fixture for instantiating, so I return it from a class-scoped fixture, like this:
@pytest.fixture(scope='class')
def class_data():
return pd.DataFrame({'A': [1,2,3], 'B': [4,5,6]})
class TestClass:
@pytest.fixture(scope='class')
def obj(self, class_data):
return Class(class_data)
def test_state1(self, obj):
obj.add1(5)
print(obj.tab1, obj.tab2)
assert obj.tab1.equals(pd.Series([1, 2, 3])+5)
def test_state2(self, obj):
obj.mul2(2)
print(obj.tab1, obj.tab2)
assert obj.tab2.equals(pd.Series([4, 5, 6])*2)
So far so good, but what if I have so many methods - I don't want to pass it individually to each method. Thus following this SO answer, I tried to create a autouse
fixture (with class scope), like this:
class TestClass:
@pytest.fixture(autouse=True, scope='class')
def setup_the_class(self, class_data):
self.obj = Class(class_data)
def test_state1(self):
self.obj.add1(5)
print(self.obj.tab1, self.obj.tab2)
assert self.obj.tab1.equals(pd.Series([1, 2, 3])+5)
def test_state2(self):
self.obj.mul2(2)
print(self.obj.tab1, self.obj.tab2)
assert self.obj.tab2.equals(pd.Series([4, 5, 6])*2)
But it gives AttributeError: 'TestClass' object has no attribute 'obj'
.
I don't understand why this happens? Even pytest docs mentions:
The class-level transact fixture is marked with
autouse=True
which implies that all test methods in the class will use this fixture without a need to state it in the test function signature or with a class-levelusefixtures
decorator.
Can anyone explain to me what is going wrong here and how autouse
fixtures actually work? Is there a better way to do what I'm trying to achieve?
Besides, this autouse
fixture approach also seems useful to me because it allows me (in theory) to define several other objects which use a fixture for their creation (like obj
does) as members of TestClass (as self.<some_variable>
) so that I will not need to define each of them in the separate class-scoped fixture that returns them. This theory is coming from my observation (assumption perhaps) that fixtures with autouse
(or usefixtures
) do not return/yield anything they just change the state of an entity that will be used by test methods, indirectly i.e. without passing as an argument. So my second question is whether I'm thinking in the right direction or not?