有没有办法在不使用 __init__ 的情况下在实例初始化时自动 运行 方法?
Is there a way to run a method automatically on the initialization of an instance without using __init__?
我正在用 Pytest 编写一些单元测试。如果我希望自动收集它们,我必须避免使用 __init__
构造函数。 (如果有办法让 Pytest 使用 __init__
构造函数收集测试,我会将其作为替代有用的答案。)
我的单元测试有一些共同的变量和方法。现在我有基础测试 class TestFoo,子测试 class TestBar(TestFoo),和孙测试 class TestBaz(TestBar)。由于我没有 init 方法,现在我正在调用一个 setup() 方法,该方法将一堆变量分配给 class 实例,作为每个测试方法的一部分。
看起来像:
Class TestBaz(TestBar):
def setup():
super().setup()
# do some other stuff
def test_that_my_program_works(self):
self.setup()
my_program_works = do_stuff()
assert my_program_works
但这很丑陋,我想知道是否有办法解决它。我开始工作的一件事——我制作了这个装饰器函数来装饰每个方法:
def setup(cls):
def inner_function(func):
@wraps(func)
def wrapper(*args, **kwargs):
cls.set_up()
return func(*args, **kwargs)
return wrapper
return inner_function
但是
@setup
def test_that_my_program_works():
也好不了多少。当我意识到根本上我不想或不需要包装每个方法时,我有点在杂草中阅读有关 metaclasses 并试图弄清楚如何更安静地包装每个方法。我只想要一个在 class 初始化时自动执行的方法。我想要 __init__
没有 __init__
。
有办法吗?
如您所见,py.test 有其他方法 运行 设置 class-scoped 方法。
你可能会 运行 那些,因为它们保证在每个(测试)方法调用之间的正确点是 运行 - 因为人们无法控制何时
py.test 实例化这样一个 class.
备案,只需在class中添加一个setup
方法(方法名称为all-lower大小写),如:
class Test1:
def setup(self):
self.a = 1
def test_blah(self):
assert self.a == 1
但是,正如您询问的有关 metaclasses 的问题,是的,metaclass 可以创建 "custom method equivalent to __init__
"。
创建新对象时,即在Python中实例化class时,就好像调用了class本身一样。内部发生的是调用元 class 的 __call__
方法,并传递参数以创建实例。
此方法然后 运行 传递这些参数的 class' __new__
和 __init__
方法,以及 returns [=16 返回的值=].
从 type
继承的元 class 可以覆盖 __call__
以添加额外的 __init__
- 类似调用,其代码只是:
class Meta(type):
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
custom_init = getattr(instance, "__custom_init__", None)
if callable(custom_init):
custom_init(*args, **kw)
return instance
我用 pytest 运行 的文件中的一个小 class 尝试了这个,它工作正常:
class Test2(metaclass=Meta):
def __custom_init__(self):
self.a = 1
def test_blah(self):
assert self.a == 1
赛程
您也可以为 method-level setup/teardown 使用自动装置。我更喜欢使用固定装置,因为它们的灵活性——你可以定义 class-specific 方法 setup/teardown (运行 用于每个测试方法)或 method-specific setup/teardown (运行 仅用于特定测试)需要 if/when。示例:
import pytest
class TestFoo:
@pytest.fixture(autouse=True)
def foo(self):
print('\nTestFoo instance setting up')
yield
print('TestFoo instance tearing down')
class TestBar(TestFoo):
@pytest.fixture(autouse=True)
def bar(self, foo):
print('TestBar instance setting up')
yield
print('TestBar instance tearing down')
class TestBaz(TestBar):
@pytest.fixture(autouse=True)
def baz(self, bar):
print('TestBaz instance setting up')
yield
print('\nTestBaz instance tearing down')
def test_eggs(self):
assert True
def test_bacon(self):
assert True
测试执行产量:
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
请注意,我通过 arg 依赖项指定了夹具执行顺序(例如 def bar(self, foo):
所以 bar
在 foo
之后执行);如果省略参数,则无法保证执行顺序 foo -> bar -> baz
。如果不需要显式排序,可以安全地省略 fixture args。
上面的例子,扩展了 setup/teardown 仅适用于 TestBaz::test_bacon
:
class TestBaz(TestBar):
@pytest.fixture(autouse=True)
def baz(self, bar):
print('TestBaz instance setting up')
yield
print('\nTestBaz instance tearing down')
@pytest.fixture
def bacon_specific(self):
print('bacon specific test setup')
yield
print('\nbacon specific teardown')
def test_eggs(self):
assert True
@pytest.mark.usefixtures('bacon_specific')
def test_bacon(self):
assert True
执行收益:
...
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 是通过将夹具范围调整为 class
:
来实现的
class TestFoo:
@pytest.fixture(autouse=True, scope='class')
def foo(self):
print('\nTestFoo instance setting up')
yield
print('TestFoo instance tearing down')
class TestBar(TestFoo):
@pytest.fixture(autouse=True, scope='class')
def bar(self, foo):
print('TestBar instance setting up')
yield
print('TestBar instance tearing down')
class TestBaz(TestBar):
@pytest.fixture(autouse=True, scope='class')
def baz(self, bar):
print('TestBaz instance setting up')
yield
print('\nTestBaz instance tearing down')
def test_eggs(self):
assert True
def test_bacon(self):
assert True
执行:
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 方法 setup/teardown
您可以使用 xUnit-style 设置,特别是 Method and function level setup/teardown;这些是常用的 class 方法并支持继承。示例:
class TestFoo:
def setup_method(self):
print('\nTestFoo::setup_method called')
def teardown_method(self):
print('TestFoo::teardown_method called')
class TestBar(TestFoo):
def setup_method(self):
super().setup_method()
print('TestBar::setup_method called')
def teardown_method(self):
print('TestBar::teardown_method called')
super().teardown_method()
class TestBaz(TestBar):
def setup_method(self):
super().setup_method()
print('TestBaz::setup_method called')
def teardown_method(self):
print('\nTestBaz::teardown_method called')
super().teardown_method()
def test_eggs(self):
assert True
def test_bacon(self):
assert True
测试执行产量:
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
我正在用 Pytest 编写一些单元测试。如果我希望自动收集它们,我必须避免使用 __init__
构造函数。 (如果有办法让 Pytest 使用 __init__
构造函数收集测试,我会将其作为替代有用的答案。)
我的单元测试有一些共同的变量和方法。现在我有基础测试 class TestFoo,子测试 class TestBar(TestFoo),和孙测试 class TestBaz(TestBar)。由于我没有 init 方法,现在我正在调用一个 setup() 方法,该方法将一堆变量分配给 class 实例,作为每个测试方法的一部分。
看起来像:
Class TestBaz(TestBar):
def setup():
super().setup()
# do some other stuff
def test_that_my_program_works(self):
self.setup()
my_program_works = do_stuff()
assert my_program_works
但这很丑陋,我想知道是否有办法解决它。我开始工作的一件事——我制作了这个装饰器函数来装饰每个方法:
def setup(cls):
def inner_function(func):
@wraps(func)
def wrapper(*args, **kwargs):
cls.set_up()
return func(*args, **kwargs)
return wrapper
return inner_function
但是
@setup
def test_that_my_program_works():
也好不了多少。当我意识到根本上我不想或不需要包装每个方法时,我有点在杂草中阅读有关 metaclasses 并试图弄清楚如何更安静地包装每个方法。我只想要一个在 class 初始化时自动执行的方法。我想要 __init__
没有 __init__
。
有办法吗?
如您所见,py.test 有其他方法 运行 设置 class-scoped 方法。 你可能会 运行 那些,因为它们保证在每个(测试)方法调用之间的正确点是 运行 - 因为人们无法控制何时 py.test 实例化这样一个 class.
备案,只需在class中添加一个setup
方法(方法名称为all-lower大小写),如:
class Test1:
def setup(self):
self.a = 1
def test_blah(self):
assert self.a == 1
但是,正如您询问的有关 metaclasses 的问题,是的,metaclass 可以创建 "custom method equivalent to __init__
"。
创建新对象时,即在Python中实例化class时,就好像调用了class本身一样。内部发生的是调用元 class 的 __call__
方法,并传递参数以创建实例。
此方法然后 运行 传递这些参数的 class' __new__
和 __init__
方法,以及 returns [=16 返回的值=].
从 type
继承的元 class 可以覆盖 __call__
以添加额外的 __init__
- 类似调用,其代码只是:
class Meta(type):
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
custom_init = getattr(instance, "__custom_init__", None)
if callable(custom_init):
custom_init(*args, **kw)
return instance
我用 pytest 运行 的文件中的一个小 class 尝试了这个,它工作正常:
class Test2(metaclass=Meta):
def __custom_init__(self):
self.a = 1
def test_blah(self):
assert self.a == 1
赛程
您也可以为 method-level setup/teardown 使用自动装置。我更喜欢使用固定装置,因为它们的灵活性——你可以定义 class-specific 方法 setup/teardown (运行 用于每个测试方法)或 method-specific setup/teardown (运行 仅用于特定测试)需要 if/when。示例:
import pytest
class TestFoo:
@pytest.fixture(autouse=True)
def foo(self):
print('\nTestFoo instance setting up')
yield
print('TestFoo instance tearing down')
class TestBar(TestFoo):
@pytest.fixture(autouse=True)
def bar(self, foo):
print('TestBar instance setting up')
yield
print('TestBar instance tearing down')
class TestBaz(TestBar):
@pytest.fixture(autouse=True)
def baz(self, bar):
print('TestBaz instance setting up')
yield
print('\nTestBaz instance tearing down')
def test_eggs(self):
assert True
def test_bacon(self):
assert True
测试执行产量:
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
请注意,我通过 arg 依赖项指定了夹具执行顺序(例如 def bar(self, foo):
所以 bar
在 foo
之后执行);如果省略参数,则无法保证执行顺序 foo -> bar -> baz
。如果不需要显式排序,可以安全地省略 fixture args。
上面的例子,扩展了 setup/teardown 仅适用于 TestBaz::test_bacon
:
class TestBaz(TestBar):
@pytest.fixture(autouse=True)
def baz(self, bar):
print('TestBaz instance setting up')
yield
print('\nTestBaz instance tearing down')
@pytest.fixture
def bacon_specific(self):
print('bacon specific test setup')
yield
print('\nbacon specific teardown')
def test_eggs(self):
assert True
@pytest.mark.usefixtures('bacon_specific')
def test_bacon(self):
assert True
执行收益:
...
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 是通过将夹具范围调整为 class
:
class TestFoo:
@pytest.fixture(autouse=True, scope='class')
def foo(self):
print('\nTestFoo instance setting up')
yield
print('TestFoo instance tearing down')
class TestBar(TestFoo):
@pytest.fixture(autouse=True, scope='class')
def bar(self, foo):
print('TestBar instance setting up')
yield
print('TestBar instance tearing down')
class TestBaz(TestBar):
@pytest.fixture(autouse=True, scope='class')
def baz(self, bar):
print('TestBaz instance setting up')
yield
print('\nTestBaz instance tearing down')
def test_eggs(self):
assert True
def test_bacon(self):
assert True
执行:
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 方法 setup/teardown
您可以使用 xUnit-style 设置,特别是 Method and function level setup/teardown;这些是常用的 class 方法并支持继承。示例:
class TestFoo:
def setup_method(self):
print('\nTestFoo::setup_method called')
def teardown_method(self):
print('TestFoo::teardown_method called')
class TestBar(TestFoo):
def setup_method(self):
super().setup_method()
print('TestBar::setup_method called')
def teardown_method(self):
print('TestBar::teardown_method called')
super().teardown_method()
class TestBaz(TestBar):
def setup_method(self):
super().setup_method()
print('TestBaz::setup_method called')
def teardown_method(self):
print('\nTestBaz::teardown_method called')
super().teardown_method()
def test_eggs(self):
assert True
def test_bacon(self):
assert True
测试执行产量:
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