如何正确设置和拆除我的pytest测试类?

245 投票
8 回答
381208 浏览
提问于 2025-04-27 22:52

我正在使用Selenium进行端到端测试,但我不太明白如何使用setup_classteardown_class这两个方法。

我想在setup_class方法中设置浏览器,然后执行一系列定义为类方法的测试,最后在teardown_class方法中关闭浏览器。

但是从逻辑上看,这似乎不是个好办法,因为实际上我的测试是针对对象的,而不是类。我在每个测试方法中传递self参数,这样我就可以访问对象的变量:

class TestClass:
  
    def setup_class(cls):
        pass
        
    def test_buttons(self, data):
        # self.$attribute can be used, but not cls.$attribute?  
        pass
        
    def test_buttons2(self, data):
        # self.$attribute can be used, but not cls.$attribute?
        pass
        
    def teardown_class(cls):
        pass
    

而且,似乎为类创建浏览器实例也不太正确……应该为每个对象单独创建一个,对吧?

所以,我需要使用__init____del__方法,而不是setup_classteardown_class吗?

暂无标签

8 个回答

23

如果你在代码中加上 @classmethod 装饰器,它应该就能像你预期的那样正常工作。

@classmethod 
def setup_class(cls):
    "Runs once per class"

@classmethod 
def teardown_class(cls):
    "Runs at end of class"

你可以查看这个链接了解更多信息:http://pythontesting.net/framework/pytest/pytest-xunit-style-fixtures/

32

正如@Bruno所建议的,使用pytest的“fixture”是一种很方便的解决方案,适用于测试类或者简单的测试函数。这里有一个测试python2.7函数的例子

import pytest

@pytest.fixture(scope='function')
def some_resource(request):
    stuff_i_setup = ["I setup"]

    def some_teardown():
        stuff_i_setup[0] += " ... but now I'm torn down..."
        print stuff_i_setup[0]
    request.addfinalizer(some_teardown)

    return stuff_i_setup[0]

def test_1_that_needs_resource(some_resource):
    print some_resource + "... and now I'm testing things..."

所以,运行 test_1... 会产生:

I setup... and now I'm testing things...
I setup ... but now I'm torn down...

注意到 stuff_i_setup 在这个“fixture”中被引用,这样就可以在测试开始前准备这个对象,并在测试结束后清理它。你可以想象,这对于一些需要保持状态的对象,比如一个假设中的数据库或者某个连接,都是很有用的,因为在每次测试运行之前都需要清理这些对象,以确保测试之间是相互独立的。

88

这可能会对你有帮助:http://docs.pytest.org/en/latest/xunit_setup.html

在我的测试套件中,我把测试用例分成了几个类。为了处理这些类中所有测试用例的准备和清理工作,我使用了 setup_class(cls)teardown_class(cls) 这两个方法。

而对于每个测试用例的准备和清理工作,我则使用 setup_method(method)teardown_method(methods)

举个例子:

lh = <got log handler from logger module>

class TestClass:
    @classmethod
    def setup_class(cls):
        lh.info("starting class: {} execution".format(cls.__name__))

    @classmethod
    def teardown_class(cls):
        lh.info("starting class: {} execution".format(cls.__name__))

    def setup_method(self, method):
        lh.info("starting execution of tc: {}".format(method.__name__))

    def teardown_method(self, method):
        lh.info("starting execution of tc: {}".format(method.__name__))

    def test_tc1(self):
        <tc_content>
        assert 

    def test_tc2(self):
        <tc_content>
        assert

现在,当我运行测试时,在执行 TestClass 的时候,它会记录开始执行和结束执行的详细信息,方法也是一样。

你可以在相应的位置添加其他的准备和清理步骤。

希望这对你有帮助!

113

当你提到 "作为类方法定义的测试" 时,你是指真正的 类方法(这些方法的第一个参数是)还是普通的方法(这些方法的第一个参数是实例)呢?

因为你的例子中测试方法使用了 self,我猜测你是指普通的方法,所以你只需要使用 setup_method 来替代:

class Test:

    def setup_method(self, test_method):
        # configure self.attribute

    def teardown_method(self, test_method):
        # tear down self.attribute

    def test_buttons(self):
        # use self.attribute for test

测试方法的实例会被传递给 setup_methodteardown_method,不过如果你的设置/清理代码不需要知道测试的上下文,这个实例可以忽略不计。更多信息可以在 这里 找到。

我还建议你了解一下 py.test 的 fixtures,因为它们是一个更强大的概念。

236

根据Fixture的最终处理/执行清理代码,目前的最佳实践是在设置和清理时使用yield而不是return

import pytest

@pytest.fixture()
def resource():
    print("setup")
    yield "resource"
    print("teardown")

class TestResource:
    def test_that_depends_on_resource(self, resource):
        print("testing {}".format(resource))

运行这个代码会得到

$ py.test --capture=no pytest_yield.py
=== test session starts ===
platform darwin -- Python 2.7.10, pytest-3.0.2, py-1.4.31, pluggy-0.3.1
collected 1 items

pytest_yield.py setup
testing resource
.teardown


=== 1 passed in 0.01 seconds ===

另一种编写清理代码的方法是,在你的fixture函数中接受一个request上下文对象,然后调用它的request.addfinalizer方法,并传入一个执行清理的函数,这样可以执行一次或多次清理:

import pytest

@pytest.fixture()
def resource(request):
    print("setup")

    def teardown():
        print("teardown")
    request.addfinalizer(teardown)
    
    return "resource"

class TestResource:
    def test_that_depends_on_resource(self, resource):
        print("testing {}".format(resource))

撰写回答