有没有一种方法可以在初始化实例时自动运行一个方法,而不必使用init悱?

2024-10-05 10:21:10 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在用Pytest编写一些单元测试。如果我想自动收集它们,我必须避免__init__构造函数。(如果有一种方法可以使用__init__构造函数使Pytest收集测试,我会把它作为另一个有用的答案。)

我的单元测试有一些共同的变量和方法。现在我有了基本测试类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

但这很难看,我想知道有没有办法绕过它。我做了一件事——我创建了这个decorator函数来装饰每个方法:

^{pr2}$

但是

@setup
def test_that_my_program_works():

不是更好。当我意识到从根本上说我不想或者不需要包装每一个方法时,我在阅读关于元类的文章,并试图找出如何更安静地包装每个方法。我只需要一个在类初始化时自动执行的方法。我想要__init__没有__init__。在

有办法吗?在


Tags: 方法testinitpytestmydefsetup单元测试
2条回答

就像你看起来的那样,py.测试有其他方法来运行类作用域方法的安装程序。 您可能会运行这些,因为它们被保证在每次(测试)方法调用之间的正确点上运行—因为您无法控制何时调用 py.测试实例化这样的类。在

{all方法的名称{1)和下面的方法一样,都是

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__类调用,其代码如下:

^{pr2}$

我用pytest运行的一个文件中的一个小类尝试了这个方法,它只起作用:

class Test2(metaclass=Meta):
    def __custom_init__(self):
        self.a = 1
    def test_blah(self):
        assert self.a == 1

固定装置

也可以将自动使用装置用于方法级别的设置/拆卸。由于fixture的灵活性,我更喜欢使用fixture—如果需要,您可以定义特定于类的方法setup/teardown(为每个测试方法运行)或方法特定的setup/teardown(仅为特定测试运行)。示例:

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

测试执行结果:

^{pr2}$

请注意,我通过arg依赖项指定fixture的执行顺序(例如def bar(self, foo):所以bar是在foo之后执行的);如果省略参数,执行顺序foo -> bar -> baz就不能得到保证。如果不需要显式排序,可以安全地省略fixture参数。在

上面的示例,仅针对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

通过将fixture范围调整为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方法设置/拆卸

您可以使用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

相关问题 更多 >

    热门问题