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

时间:2017-10-16 08:33:32

标签: python automation pytest nose

我的自动化框架使用pytest setup / teardown类型的测试而不是fixtures。我也有几个级别的课程:

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

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

TestClass - 持有实际测试

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

我想在所有设置/拆卸中添加数据库报告。即我希望通用BaseClass setup_method将为测试创建一个DB条目,teardown_method将改变带有结果的条目。我已经尝试但是我似乎无法在运行时将方法中的当前运行测试的值排除在方法之外。甚至有可能吗?如果没有,我怎么能这样做?

样品: (在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

(在test_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)

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

1 个答案:

答案 0 :(得分:3)

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

更长一点:这些单元测试风格的设置&amp;拆卸只是为了兼容unittest式测试。它们不支持pytest的夹具,这使得pytest很好。

由于这个原因,pytest和pytest的unittest插件都没有为这些设置/拆卸方法提供上下文。如果您有requestfunction或其他一些上下文对象,则可以通过request.getfuncargvalue('my_fixture_name')动态获取灯具的值。

但是,您拥有的只有self / clsmethod作为测试方法对象本身(即不是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()

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

另外请注意不要在item.instance中保存字段。稍后将创建该类的实例,您必须使用setup_instance/teardown_instance方法。否则,保存的实例字段不会被保留,并且在self._item中无法用作setup_method()

这是执行:

============ 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 ============