我正在用Pytest编写一些单元测试。如果要自动收集它们,则必须避免使用__init__
构造函数。 (如果有一种方法可以让Pytest使用__init__
构造函数来收集测试,我将把它作为另一个有用的答案。)
我的单元测试有一些共同的变量和方法。现在,我有基本测试类TestFoo,子测试类TestBar(TestFoo)和孙子测试类TestBaz(TestBar)。因为我没有init方法,所以现在我正在调用setup()方法,该方法将一堆变量分配给类实例,作为每个测试方法的一部分。
它看起来像:
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():
没有那么好。我有点像杂草丛书中有关元类的阅读,并试图弄清楚当我意识到根本上我不需要或不需要包装每个方法时,我将如何更安静地包装每个方法。我只想要一个在类初始化时自动执行的方法。我想要没有__init__
的{{1}}。
有没有办法做到这一点?
答案 0 :(得分:3)
您也可以将自动使用的夹具用于方法级的设置/拆卸。我更喜欢使用夹具,因为它们具有灵活性-您可以在需要时定义特定于类的方法设置/拆卸(针对每个测试方法运行)或定义特定于方法的设置/拆卸(仅针对特殊测试运行)。例子:
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
。如果不需要显式排序,则可以安全地省略灯具args。
上面的示例扩展了仅针对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
通过将灯具范围调整为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风格的设置,尤其是Method and function level setup/teardown;这些是常用的类方法,支持继承。示例:
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
答案 1 :(得分:1)
如您所见,py.test还有其他方法可以为类作用域的方法运行安装程序。 您可能会运行它们,因为它们保证在每个(测试)方法调用之间的正确点运行-因为一个人无法控制何时 py.test实例化了此类。
为进行记录,只需向类中添加一个setup
方法(方法名称为小写),例如:
class Test1:
def setup(self):
self.a = 1
def test_blah(self):
assert self.a == 1
但是,当您询问元类时,是的,元类可以用来创建“等效于__init__
的自定义方法”。
创建新对象时,即在Python中实例化类时,就好像该类本身已被调用。内部发生的情况是调用了元类的__call__
方法,并传递了用于创建实例的参数。
然后该方法运行传递这些参数的类的__new__
和__init__
方法,并返回__new__
返回的值。
从type
继承的元类可以重写__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 Test2(metaclass=Meta):
def __custom_init__(self):
self.a = 1
def test_blah(self):
assert self.a == 1