如何通过测试正确设置和拆卸我的 pytest 类?

Posted

技术标签:

【中文标题】如何通过测试正确设置和拆卸我的 pytest 类?【英文标题】:How do I correctly setup and teardown for my pytest class with tests? 【发布时间】:2014-12-11 21:04:06 【问题描述】:

我正在使用 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

【问题讨论】:

【参考方案1】:

根据Fixture finalization / executing teardown code,当前设置和拆卸的最佳实践是使用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 ===

另一种编写拆卸代码的方法是将request-context object 接受到您的fixture 函数中,并使用执行一次或多次拆卸的函数调用其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))

【讨论】:

所以你把它复制到你需要资源的每个测试文件中? 这不是课程设置,对吧?它会在类中的每个测试方法之前执行。 在这种特殊情况下,它仅在用作测试方法中的参数时才会执行。例如test_that_depends_on_resource(self, resource) 中的 resource 参数 请注意,您可以将夹具范围设置为 'class' 并将 autouse 设置为 true 以确保为每个类调用一次代码,而不必将其作为参数包含在任何测试调用中:`` ` pytest.fixture(scope="class", autouse=True) def resource(): print("setup") yield "resource" print("teardown") `` 链接更新:docs.pytest.org/en/stable/…【参考方案2】:

当你写“定义为类方法的测试”时,你真的是指类方法(接收它的作为第一个参数的方法吗? ) 还是只是常规方法(接收 instance 作为第一个参数的方法)?

由于您的示例使用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,但如果您的设置/拆卸代码不需要知道测试上下文,则可以忽略它。更多信息可以在here找到。

我还建议您熟悉 py.test 的 fixtures,因为它们是一个更强大的概念。

【讨论】:

Fixtures 比类方法弱:它们不允许破坏不是由它们创建的对象(这通常是真正需要的)。除此之外,感谢您提供信息。 在将代码库从 pytest 的 3.0.x 版本升级到 4.x 变体时,这让我大吃一惊。一些较旧的代码使用 setup_class 和需要现代化的模拟方法等。 setup_class(self, foo, bar) --> setup_method(self,function,foo,bar)【参考方案3】:

这可能有助于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 开始执行时,它会记录开始执行时间、结束执行时间以及方法的详细信息..

您可以在各自的位置添加其他设置和拆卸步骤。

希望对你有帮助!

【讨论】:

嗨@Kiran,setup_classsetup_method 有什么区别? @imsrgadich 当您将测试用例组织成类时,_class 用于类的设置和拆卸步骤,_method 是每个测试的相应步骤案例方法。 该死的......现在我明白了!被困在上面几个小时。所以,要正确看待事情。整个班级的&lt;setup/teardown&gt;_class。在这里,可以是设置到 DB 的链接或加载数据文件。然后,每个测试用例都可以有自己的设置,格式为&lt;setup/teardown&gt;_method。现在事情已经很清楚了。非常感谢!【参考方案4】:

正如@Bruno 所建议的,使用 pytest 固定装置是另一种可用于测试类甚至简单测试功能的解决方案。 Here's an example testing python2.7 functions:

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,允许该对象为setuptorn down,用于与之交互的测试。您可以想象这对于持久性对象(例如假设的数据库或某些连接)很有用,必须在每次测试运行之前清除它们以保持它们隔离。

【讨论】:

【参考方案5】:

如果您添加 @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/

【讨论】:

这几乎就是文档中出现的内容。我在文档中遇到的问题是我很难理解上下文:self 传统上被称为 self,而不是 cls,所以这对我来说似乎很奇怪,脱离了类本身的上下文。 Kiran(上图)提供了这种背景。 @Cognitiaclaeves "self 传统上被称为 self,而不是 cls" 是的,self 用于实例方法,其中第一个参数是具体的对象实例其中方法操作正在发生,而cls用于@classmethods,它们绑定到类而不是类的实例(即对象)。【参考方案6】:
import pytest
class Test:
    @pytest.fixture()
    def setUp(self):
        print("setup")
        yield "resource"
        print("teardown")

    def test_that_depends_on_resource(self, setUp):
        print("testing ".format(setUp))

为了运行:

pytest nam_of_the_module.py -v 

【讨论】:

【参考方案7】:

我不确定我是否在您的原始问题中了解了使用 Selenium 的细节,但如果您只是询问如何使用更经典的 setUp/tearDown 样式,Pytest 支持大多数单元测试功能,因此您可以做点什么喜欢:

import unittest


class TestHello(unittest.TestCase):

    def setUp(self):
        print('running setUp')

    def test_one(self):
        print('running test_one')

    def test_two(self):
        print('running test_two')

    def tearDown(self):
        print('running tearDown')

产生:

$ pytest -s -v
====================== test session starts =======================
platform linux -- Python 3.8.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- /gnu/store/nckjv3ccwdi6096j478gvns43ssbls2p-python-wrapper-3.8.2/bin/python
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/tmp/test/.hypothesis/examples')
rootdir: /tmp/test
plugins: hypothesis-5.4.1
collected 2 items                                                

test_hw.py::TestHello::test_one running setUp
running test_one
running tearDown
PASSED
test_hw.py::TestHello::test_two running setUp
running test_two
running tearDown
PASSED

【讨论】:

以上是关于如何通过测试正确设置和拆卸我的 pytest 类?的主要内容,如果未能解决你的问题,请参考以下文章

在 django 中设置拆卸登录注销的正确方法

在 py.test 中的每个测试之前和之后运行代码?

Test::Unit 中的全局设置和拆卸块

UnitTest 我应该使用拆卸和设置吗

以下哪个装饰器用于在使用鼻子时将用户定义的设置和拆卸功能分配给测试功能?

如何在 RobotFramework 中进行多行测试设置或拆卸而不创建新关键字?