pytest:在 setup/teardown 方法期间获取参数化值

pytest: get parametrize values during setup/teardown method

我的自动化框架使用 pytest setup/teardown 类型的测试而不是固定装置。我还有好几级classes:

BaseClass - 最高,所有测试都来自它

FeatureClass - 中等,所有与程序功能相关的测试都继承自它

TestClass - 举行实际测试

编辑,为了举例,我将数据库调用更改为简单的打印

我想在所有 setup/teardown 中添加数据库报告。也就是说,我希望一般 BaseClass setup_method 将为测试创建一个数据库条目,而 teardown_method 将用结果更改条目。我已经尝试过,但我似乎无法在 运行 时间内摆脱当前 运行ning 测试的值。有可能吗?如果没有,我该怎么办?

样本: (在 base.py)

class Base(object):

    test_number = 0
    def setup_method(self, method):
        Base.test_number += 1
        self.logger.info(color.Blue("STARTING TEST"))
        self.logger.info(color.Blue("Current Test: {}".format(method.__name__)))
        self.logger.info(color.Blue("Test Number: {}".format(self.test_number)))
        # --->here i'd like to do something with the actual test parameters<---
        self.logger.info("print parameters here")

    def teardown_method(self, method):
        self.logger.info(color.Blue("Current Test: {}".format(method.__name__)))
        self.logger.info(color.Blue("Test Number: {}".format(self.test_number)))
        self.logger.info(color.Blue("END OF TEST"))

(在my_feature.py)

class MyFeature(base.Base):

    def setup_method(self, method):
        # enable this feature in program
        return True

(测试中_my_feature.py)

class TestClass(my_feature.MyFeature):

    @pytest.mark.parametrize("fragment_length", [1,5,10])    
    def test_my_first_test(self):
        # do stuff that is changed based on fragment_length
        assert verify_stuff(fragment_length)

那么如何获取测试框架的基本父 class setup_method 中的参数?

简短的回答:不,你不能这样做。是的,您可以解决它。


有点长:这些单元测试风格的设置和拆卸只是为了与单元测试风格的测试兼容。他们不支持 pytest 的 fixture,这使 pytest 很好。

因此,pytest 和 pytest 的 unittest 插件都没有为这些 setup/teardown 方法提供上下文。如果您有 requestfunction 或其他一些上下文对象,您可以通过 request.getfuncargvalue('my_fixture_name').

动态获取灯具的值

但是,您所拥有的只是 self/cls,并且 method 作为测试方法对象本身(即不是 pytest 的节点)。

如果你查看 _pytest/unittest.py 插件的内部,你会发现这段代码:

class TestCaseFunction(Function):
    _excinfo = None

    def setup(self):
        self._testcase = self.parent.obj(self.name)
        self._fix_unittest_skip_decorator()
        self._obj = getattr(self._testcase, self.name)
        if hasattr(self._testcase, 'setup_method'):
            self._testcase.setup_method(self._obj)
        if hasattr(self, "_request"):
            self._request._fillfixtures()

首先,请注意 setup_method() 被称为与 pytest 的对象完全隔离(例如 self 作为测试节点)。

其次,请注意在调用 setup_method() 之后准备固定装置。因此,即使您可以访问它们,它们也不会准备好。

所以,一般来说,如果不采取一些技巧,您就无法做到这一点。


为了骗人,你必须定义一次pytest hook/hookwrapper,并记住正在执行的pytest节点:

conftest.py 或任何其他插件:

import pytest

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item, nextitem):
    item.cls._item = item
    yield

test_me.py:

import pytest


class Base(object):
    def setup_method(self, method):
        length = self._item.callspec.getparam('fragment_length')
        print(length)


class MyFeature(Base):
    def setup_method(self, method):
        super().setup_method(method)


class TestClass(MyFeature):
    @pytest.mark.parametrize("fragment_length", [1,5,10])    
    def test_my_first_test(self, fragment_length):
        # do stuff that is changed based on fragment_length
        assert True  # verify_stuff(fragment_length)

另请注意,由于显而易见的原因,MyFeature.setup_method() 必须调用父级的 super(...).setup_method()

cls._item 将在每个调用规范(即每个带有每个参数的函数调用)上设置。如果愿意,您还可以将项目或特定参数放入其他全局状态。

另外注意不要保存 item.instance 中的字段。 class 的实例将在稍后创建,您必须为此使用 setup_instance/teardown_instance 方法。否则,已保存实例的字段不会保留,并且在 setup_method().

中无法使用 self._item

这里是执行:

============ test session starts ============
......
collected 3 items                                                                                                                                                                 

test_me.py::TestClass::test_my_first_test[1] 1
PASSED
test_me.py::TestClass::test_my_first_test[5] 5
PASSED
test_me.py::TestClass::test_my_first_test[10] 10
PASSED

============ 3 passed in 0.04 seconds ============