@ 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等等。
相反,会发生的是测试类
我假设正在发生的是参数化优先于通过类中的函数继续进行。问题是,是否有可能覆盖此行为或以某种方式解决它,因为当前情况使得将类标记为增量对我来说完全没用。
(处理这个问题的唯一方法就是一遍又一遍地复制粘贴类的代码,每次使用不同的参数?这个想法对我来说是令人厌恶的)
答案 0 :(得分:1)
标题A quick port of “testscenarios”
这是那里列出的代码以及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%测试,但正在使用并且可以满足我的需求。