Is There A Way To Run A Method Automatically On The Initialization Of An Instance Without Using __init__?
Solution 1:
Fixtures
You can use autouse fixtures also for method-level setup/teardown. I would prefer using fixtures because of their flexibility - you can define class-specific method setup/teardown (running for every test method) or method-specific setup/teardown (running for a speficic test only) if/when needed. Examples:
import pytest
classTestFoo:
@pytest.fixture(autouse=True)deffoo(self):
print('\nTestFoo instance setting up')
yieldprint('TestFoo instance tearing down')
classTestBar(TestFoo):
@pytest.fixture(autouse=True)defbar(self, foo):
print('TestBar instance setting up')
yieldprint('TestBar instance tearing down')
classTestBaz(TestBar):
@pytest.fixture(autouse=True)defbaz(self, bar):
print('TestBaz instance setting up')
yieldprint('\nTestBaz instance tearing down')
deftest_eggs(self):
assertTruedeftest_bacon(self):
assertTrue
Test execution yields:
collected 2 items
test_spam.py::TestBaz::test_eggs
TestFoo instance setting up
TestBar instance setting up
TestBaz instance setting up
PASSED
TestBaz instance tearing down
TestBar instance tearing down
TestFoo instance tearing down
test_spam.py::TestBaz::test_bacon
TestFoo instance setting up
TestBar instance setting up
TestBaz instance setting up
PASSED
TestBaz instance tearing down
TestBar instance tearing down
TestFoo instance tearing down
Notice that I specify fixture execution order via arg dependencies (e.g. def bar(self, foo):
so bar
is executed after foo
); if you omit the arguments, the execution order foo -> bar -> baz
is not guaranteed. If you don't need the explicit ordering, you can safely omit the fixture args.
The above example, extended with a setup/teardown specific for TestBaz::test_bacon
only:
classTestBaz(TestBar):
@pytest.fixture(autouse=True)defbaz(self, bar):
print('TestBaz instance setting up')
yieldprint('\nTestBaz instance tearing down')
@pytest.fixturedefbacon_specific(self):
print('bacon specific test setup')
yieldprint('\nbacon specific teardown')
deftest_eggs(self):
assertTrue @pytest.mark.usefixtures('bacon_specific')deftest_bacon(self):
assertTrue
Execution yields:
...
test_spam.py::TestBaz::test_bacon
TestFoo instance setting up
TestBar instance setting up
TestBaz instance setting up
bacon specific test setup
PASSED
bacon specific teardown
TestBaz instance tearing down
TestBar instance tearing down
TestFoo instance tearing down
One-time setup/teardown per class is achieved by adjusting the fixture scope to class
:
classTestFoo:
@pytest.fixture(autouse=True, scope='class')deffoo(self):
print('\nTestFoo instance setting up')
yieldprint('TestFoo instance tearing down')
classTestBar(TestFoo):
@pytest.fixture(autouse=True, scope='class')defbar(self, foo):
print('TestBar instance setting up')
yieldprint('TestBar instance tearing down')
classTestBaz(TestBar):
@pytest.fixture(autouse=True, scope='class')defbaz(self, bar):
print('TestBaz instance setting up')
yieldprint('\nTestBaz instance tearing down')
deftest_eggs(self):
assertTruedeftest_bacon(self):
assertTrue
Execution:
collected 2 items
test_spam2.py::TestBaz::test_eggs
TestFoo instance setting up
TestBar instance setting up
TestBaz instance setting up
PASSED
test_spam2.py::TestBaz::test_bacon PASSED
TestBaz instance tearing down
TestBar instance tearing down
TestFoo instance tearing down
xUnit method setup/teardown
You can use the xUnit-style setup, in particular the Method and function level setup/teardown; these are usual class methods and support inheritance. Example:
classTestFoo:
defsetup_method(self):
print('\nTestFoo::setup_method called')
defteardown_method(self):
print('TestFoo::teardown_method called')
classTestBar(TestFoo):
defsetup_method(self):
super().setup_method()
print('TestBar::setup_method called')
defteardown_method(self):
print('TestBar::teardown_method called')
super().teardown_method()
classTestBaz(TestBar):
defsetup_method(self):
super().setup_method()
print('TestBaz::setup_method called')
defteardown_method(self):
print('\nTestBaz::teardown_method called')
super().teardown_method()
deftest_eggs(self):
assertTruedeftest_bacon(self):
assertTrue
Test execution yields:
collected 2 items
test_spam.py::TestBaz::test_eggs
TestFoo::setup_method called
TestBar::setup_method called
TestBaz::setup_method called
PASSED
TestBaz::teardown_method called
TestBar::teardown_method called
TestFoo::teardown_method called
test_spam.py::TestBaz::test_bacon
TestFoo::setup_method called
TestBar::setup_method called
TestBaz::setup_method called
PASSED
TestBaz::teardown_method called
TestBar::teardown_method called
TestFoo::teardown_method called
Solution 2:
As you had seem, py.test have other means to run a setup for class-scoped methods. You'd probably run those, as they are guaranteed to be run at the right points between each (test) method call - as one won't have control on when py.test instantiate such a class.
For the record, just add a setup
method to the class (the method name is all-lower case), like in:
classTest1:
defsetup(self):
self.a = 1deftest_blah(self):
assert self.a == 1
However, as you asked about metaclasses, yes, a metaclass can work to create a "custom method equivalent to __init__
".
When a new object is created, that is, when class is instantiated in Python, it is as though the class itself was called. What happens internally is that the __call__
method for the metaclass is called, with the parameters passed to create the instance.
This method then runs the class' __new__
and __init__
methods passing those parameters, and returns the value returned by __new__
.
A metaclass inheriting from type
can override __call__
to add extra __init__
- like calls, and the code for that is just:
classMeta(type):
def__call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
custom_init = getattr(instance, "__custom_init__", None)
ifcallable(custom_init):
custom_init(*args, **kw)
return instance
I've tried this with a small class in a file I run with pytest, and it just works:
classTest2(metaclass=Meta):
def__custom_init__(self):
self.a = 1deftest_blah(self):
assert self.a == 1
Post a Comment for "Is There A Way To Run A Method Automatically On The Initialization Of An Instance Without Using __init__?"