如何根据pytest中的夹具参数参数化测试?



我有一个Python程序,它根据输入规范生成C代码。我正在使用pytest编写测试。当然,测试策略包括对生成的 C 代码进行一些测试。

对于这些测试,计划如下所示:

我们有一组目录
  • ,每个目录包含一个规范文件和一组适用的输入/预期输出案例。

  • 夹具将处理生成 C 代码并对其进行编译。此夹具将通过规范文件集(由测试脚本以编程方式读取(进行参数化。这样做的好处是,对于该规范下的所有测试用例,只能进行一次构建(因为构建成本很高(。

  • 测试函数将从夹具中获取GeneratedCode对象,使用特定输入运行它,并验证预期的输出。这将在一组输入/输出事例(脚本也以编程方式读取(上参数化。

这样,添加新测试用例就像添加新规范或测试用例文件一样简单。无需在测试脚本中复制和粘贴代码。

我想象它看起来像这样:

# Get the list of specification files and test cases programmatically
specification_names = get_list_of_specifications()
test_cases = dict()
for spec in specification_names:
# get_list_of_test_cases() returns a list of (input, output) tuples
test_cases[spec] = get_list_of_test_cases(spec)
class GeneratedCode:
def __init__(spec):
"""Generate the C code for spec in a temp directory"""
self.name = spec
...

def build():
"""Build the generated C code"""
...

def run(input):
"""Run the code on given input."""
...

def cleanup():
...
@pytest.fixture(scope="module", params=specification_names)
def generated_code(request):
code = GeneratedCode(request.param)
code.build()
yield code
code.cleanup()
@pytest.mark.parametrize('test_input,expected_output', test_cases[???])
def test_generated_code(generated_code, test_input, expected_output):
assert generated_code.run(test_input) == expected_output

当然,这里的问题是@pytest.mark.parametrize()不能每次都使用相同的测试用例集,因为它取决于生成代码的规范。如果我们能得到当前灯具的参数,我们可以在test_cases字典中查找它,但我不确定该怎么做,或者是否有可能。

有没有办法做到这一点?我应该有其他方法进行这些测试吗?

也许能够通过将数据作为 generated_code 元组的一部分传递回来连接数据。

@pytest.fixture(scope="module", params=specification_names)
def generated_code(spec):
code = GeneratedCode(spec)
code.build()
yield code, spec
code.cleanup()
def test_generated_code(generated_code):
code, spec = generated_code
test_input, expected_output = test_cases[spec]
assert generated_code.run(test_input) == expected_output```

我能想到的另一种方法是使用subTest,如果您可以访问unittest,Python 标准库的一部分:

import unittest
class TestSequence(unittest.TestCase):
def _setup(self, spec):
self.code = GeneratedCode(spec)
self.code.build()
def tearDown(self):
self.code.cleanup()
def test_generated_code(self):
for spec, (test_input, expected_output) in test_cases.items():
with self.subTest(spec):
self._setup(spec)
assert self.code.run(test_input) == expected_output

@pytest.mark.parametrizeindirect参数可以帮助完成这项工作。它基本上允许从测试功能参数化夹具。

specification_names = get_list_of_specifications()
test_cases = []
for spec in specification_names:
test_cases.extend([(spec, input, output) for (input, output) in
get_list_of_test_cases(spec)])
...
@pytest.fixture(scope="module")
def generated_code(request):
code = GeneratedCode(request.param)
code.build()
yield code
code.cleanup()
@pytest.mark.parametrize(
'generated_code,test_input,expected_output',
test_cases,
indirect=['generated_code'],
scope="module" # <-- This is important!
)
def test_generated_code(generated_code, test_input, expected_output):
assert generated_code.run(test_input) == expected_output

请注意parametrize装饰器中的scope="module"。如果未指定,它将默认为'function',在某些情况下(包括此情况(,这似乎优先于灯具的指定范围。

这方面的细节对我来说很模糊。关于scope@pytest.mark.parameterize意味着什么的文档不是很清楚。但是,似乎如果parametrize中的所有参数都indirect,则夹具使用自己的范围,否则使用parametrize的范围。而且,如果您有多个测试功能使用带有indirect的同一夹具,无论您指定什么,它们通常最终都会出现在不同的范围内,我不确定为什么。这是一个以前有缺陷的区域,它可能仍然是。

无论如何,上面的代码应该做你想要的,但最好将夹具范围更多地视为性能优化,而不是依赖它来进行正确的测试行为(听起来你已经在这样做了(。

最新更新