在pytest测试类

时间:2016-10-06 14:29:27

标签: python testing pytest

@ mark.incremental的目的是,如果一个测试失败,之后的测试将标记为预期失败。

但是,当我将其与参数化结合使用时,我会得到不希望的行为。

例如,在这个假代码的情况下:

//conftest.py:

def pytest_generate_tests(metafunc):
    metafunc.parametrize("input", [True, False, None, False, True])

def pytest_runtest_makereport(item, call):
    if "incremental" in item.keywords:
        if call.excinfo is not None:
            parent = item.parent
            parent._previousfailed = item

def pytest_runtest_setup(item):
    if "incremental" in item.keywords:
        previousfailed = getattr(item.parent, "_previousfailed", None)
        if previousfailed is not None:
            pytest.xfail("previous test failed (%s)" %previousfailed.name)

//test.py:
@pytest.mark.incremental
class TestClass:
    def test_input(self, input):
        assert input is not None
    def test_correct(self, input):
        assert input==True

我希望测试类能够运行

  • test_input on True,

  • 然后在True上执行test_correct,

  • 后面跟着F_的test_input,

  • 然后在False上执行test_correct,

  • 下载test_input on None,

  • 后跟(xfailed)test_correct on None等等。

相反,会发生的是测试类

  • 在True上运行test_input,
  • 然后在False上运行test_input,
  • 然后在None上运行test_input,
  • 然后
  • 将从那一点开始的所有内容标记为xfailed(包括test_corrects)。

我假设正在发生的是参数化优先于通过类中的函数继续进行。问题是,是否有可能覆盖此行为或以某种方式解决它,因为当前情况使得将类标记为增量对我来说完全没用。

(处理这个问题的唯一方法就是一遍又一遍地复制粘贴类的代码,每次使用不同的参数?这个想法对我来说是令人厌恶的)

2 个答案:

答案 0 :(得分:1)

标题A quick port of “testscenarios”

下的https://docs.pytest.org/en/latest/example/parametrize.html中描述了对此的解决方案

这是那里列出的代码以及conftest.py中的代码正在做的是它在测试类中寻找变量scenarios。当它找到变量时,它会迭代每个场景项目,并期望用id字符串标记测试和字典#argames:argvalues'

# content of conftest.py
def pytest_generate_tests(metafunc):
    idlist = []
    argvalues = []
    for scenario in metafunc.cls.scenarios:
        idlist.append(scenario[0])
        items = scenario[1].items()
        argnames = [x[0] for x in items]
        argvalues.append(([x[1] for x in items]))
    metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")

# content of test_scenarios.py
scenario1 = ('basic', {'attribute': 'value'})
scenario2 = ('advanced', {'attribute': 'value2'})

class TestSampleWithScenarios(object):
    scenarios = [scenario1, scenario2]

    def test_demo1(self, attribute):
        assert isinstance(attribute, str)

    def test_demo2(self, attribute):
        assert isinstance(attribute, str)

您还可以修改函数pytest_generate_tests以接受不同的数据类型输入。例如,如果您有一个通常传递给的列表 @pytest.mark.parametrize("varname", varval_list) 您可以通过以下方式使用相同的列表:

# content of conftest.py
def pytest_generate_tests(metafunc):
    idlist = []
    argvalues = []
    argnames = metafunc.cls.scenario_keys
    for idx, scenario in enumerate(metafunc.cls.scenario_parameters):
        idlist.append(str(idx))
        argvalues.append([scenario])
    metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")

# content of test_scenarios.py
varval_list = [a, b, c, d]
class TestSampleWithScenarios(object):
    scenario_parameters = varval_list
    scenario_keys = ['varname']

    def test_demo1(self, attribute):
        assert isinstance(attribute, str)

    def test_demo2(self, attribute):
        assert isinstance(attribute, str)

id将是一个自动生成的数字(您可以将其更改为使用您指定的内容),在此实现中,它不会处理多个参数化变量,因此您必须在单个列表中编译它们(或者cater {{ 1}}为你处理那个)

答案 1 :(得分:0)

以下解决方案不要求更改测试类

_test_failed_incremental = defaultdict(dict)


def pytest_runtest_makereport(item, call):
    if "incremental" in item.keywords:
        if call.excinfo is not None and call.excinfo.typename != "Skipped":
            param = tuple(item.callspec.indices.values()) if hasattr(item, "callspec") else ()
            _test_failed_incremental[str(item.cls)].setdefault(param, item.originalname or item.name)


def pytest_runtest_setup(item):
    if "incremental" in item.keywords:
        param = tuple(item.callspec.indices.values()) if hasattr(item, "callspec") else ()
        originalname = _test_failed_incremental[str(item.cls)].get(param)
        if originalname:
            pytest.xfail("previous test failed ({})".format(originalname))

它的工作方式是保留一个字典,该字典将每个类和每个参数化输入的索引的失败测试作为键(以及失败的测试方法的名称作为值)。 在您的示例中,字典_test_failed_incremental将为

defaultdict(<class 'dict'>, {"<class 'test.TestClass'>": {(2,): 'test_input'}})

显示类test.TestClass的第三次运行(index = 2)失败。 在针对给定参数的类中运行测试方法之前,它会检查该类中是否有针对该给定参数的先前测试方法没有失败,如果是,则通过第一个失败的方法名称信息xfail测试。 >

未经100%测试,但正在使用并且可以满足我的需求。