Python 如何将参数化夹具作为参数传递给另一个夹具

Python 如何将参数化夹具作为参数传递给另一个夹具,python,pytest,fixtures,parameterized-unit-test,Python,Pytest,Fixtures,Parameterized Unit Test,我试图避免在测试中重复太多的样板文件,我想用一种更结构化的方式重写它们。假设我有两个不同的解析器,它们都可以将文本解析为文档。然后,该文档将用于其他测试。最终目标是公开一个doc()fixture,该fixture可以在其他测试中使用,其参数化方式使其运行给定解析器和文本的所有组合 @pytest.fixture def解析器_a(): 返回“parser_a”#实际上是一个解析器对象 @pytest.fixture def解析器_b(): 返回“parser_b”#实际上是一个解析器对象 @p

我试图避免在测试中重复太多的样板文件,我想用一种更结构化的方式重写它们。假设我有两个不同的解析器,它们都可以将文本解析为
文档
。然后,该文档将用于其他测试。最终目标是公开一个
doc()
fixture,该fixture可以在其他测试中使用,其参数化方式使其运行给定解析器和文本的所有组合

@pytest.fixture
def解析器_a():
返回“parser_a”#实际上是一个解析器对象
@pytest.fixture
def解析器_b():
返回“parser_b”#实际上是一个解析器对象
@pytest.fixture
def short_text():
返回“Lorem ipsum”
@pytest.fixture
def long_text():
return“如果我知道如何烤饼干,我就能让每个人都开心。”
现在的问题是,如何创建一个如下所示的
doc()
fixture:

@pytest.fixture(scope='function')
def doc_fixture(request):
    parser = request.param[0]
    text = request.param[1]
    return parser.parse(text)
@pytest.fixture(参数=??)
def文档(解析器,文本):
返回parser.parse(文本)
其中,
parser
参数化为parser_a和parser_b,
text
参数化为short_text和long_text。这意味着总共
doc
将测试解析器和文本的四种组合


关于PyTest的参数化装置的文档非常模糊,我找不到一个如何解决这个问题的答案。欢迎所有帮助。

您的设备应如下所示:

@pytest.fixture(scope='function')
def doc_fixture(request):
    parser = request.param[0]
    text = request.param[1]
    return parser.parse(text)
并按以下方式使用:

@pytest.mark.parametrize('doc_fixture', [parser_1, 'short text'], indirect=True)
def test_sth(doc_fixture):
    ...  # Perform tests
您可以使用混合和匹配参数组合

下面是另一个提供不同参数组合的示例:

from argparse import Namespace
import pytest

@pytest.fixture(scope='function')
def doc_fixture(request):
    first_arg, second_arg = request.param
    s = Namespace()
    s.one = first_arg
    s.two = second_arg
    return s


@pytest.mark.parametrize(
    'doc_fixture',
    [
        ('parserA', 'ShortText'),
        ('parserA', 'LongText'),
        ('parserB', 'ShortText'),
        ('parserB', 'LongText')
    ],
    indirect=True
)
def test_something(doc_fixture):
    assert doc_fixture == ''
以及一个示例运行结果(预期测试失败):


不确定这是否正是您所需要的,但您可以使用函数而不是装置,并将这些组合到装置中:

import pytest

class Parser:  # dummy parser for testing
    def __init__(self, name):
        self.name = name

    def parse(self, text):
        return f'{self.name}({text})'


class ParserFactory:  # do not recreate existing parsers
    parsers = {}

    @classmethod
    def instance(cls, name):
        if name not in cls.parsers:
            cls.parsers[name] = Parser(name)
        return cls.parsers[name]

def parser_a():
    return ParserFactory.instance("parser_a") 

def parser_b():
    return ParserFactory.instance("parser_b")

def short_text():
    return "Lorem ipsum"

def long_text():
    return "If I only knew how to bake cookies I could make everyone happy."


@pytest.fixture(params=[long_text, short_text])
def text(request):
    yield request.param

@pytest.fixture(params=[parser_a, parser_b])
def parser(request):
    yield request.param

@pytest.fixture
def doc(parser, text):
    yield parser().parse(text())

def test_doc(doc):
    print(doc)
生成的pytest输出为:

============================= test session starts =============================
...
collecting ... collected 4 items

test_combine_fixt.py::test_doc[parser_a-long_text] PASSED                [ 25%]parser_a(If I only knew how to bake cookies I could make everyone happy.)

test_combine_fixt.py::test_doc[parser_a-short_text] PASSED               [ 50%]parser_a(Lorem ipsum)

test_combine_fixt.py::test_doc[parser_b-long_text] PASSED                [ 75%]parser_b(If I only knew how to bake cookies I could make everyone happy.)

test_combine_fixt.py::test_doc[parser_b-short_text] PASSED               [100%]parser_b(Lorem ipsum)


============================== 4 passed in 0.05s ==============================
更新: 我为解析器添加了一个单例工厂,如注释中作为示例所述

注: 我尝试使用@hoefling建议的
pytest.lazy_fixture
。这是可行的,并且可以直接从一个fixture传递解析器和文本,但是我还不能让它以每个解析器只实例化一次的方式工作。以下是使用
pytest.lazy_fixture
时更改的代码供参考:

@pytest.fixture
def parser_a():
    return Parser("parser_a")

@pytest.fixture
def parser_b():
    return Parser("parser_b")

@pytest.fixture
def short_text():
    return "Lorem ipsum"

@pytest.fixture
def long_text():
    return "If I only knew how to bake cookies I could make everyone happy."


@pytest.fixture(params=[pytest.lazy_fixture('long_text'),
                        pytest.lazy_fixture('short_text')])
def text(request):
    yield request.param

@pytest.fixture(params=[pytest.lazy_fixture('parser_a'),
                        pytest.lazy_fixture('parser_b')])
def parser(request):
    yield request.param


@pytest.fixture
def doc(parser, text):
    yield parser.parse(text)


def test_doc(doc):
    print(doc)

你能在你的帖子中用文字解释一下如何读取
参数化
行(参数之间的关系)。fixture的名称,参数列表,indirect=True指定参数被发送到fixtrue。我认为这不是我想要的。如果我理解正确,您只会将解析器a和简短文本发送到装置,但我需要对解析器和文本的所有组合执行此操作。@BramVanroy您可以混合和匹配
pytest.mark.parametrize
的组合,编辑答案以添加更合适的示例。@salparade但现在它只显示了装置的一个级别,正确的?“ShortText”和“parserA”不就是字符串吗?我需要它们自己成为固定装置(或者,正如MrBean的回答中所说的,通过使用单例)。该插件声称拥有该功能,值得一看。谢谢你的回复。这难道不意味着解析器将被一次又一次地创建,因为它们不在fixture中吗?是的-如果不需要,这是不够的。。。稍后我会看一看。我通过使用一个全局dict解析器来规避这个问题,在您的示例中,
def parser_a()
变得类似于:
if'parser_a'不在解析器中:PARSERS['parser_a']=parser('parser_a');返回解析器['parser_a']
。这似乎有效,但我不确定是否有任何警告。是的,这总是一种可能性-使他们基本上是单身。我还没有想出另一个解决方案,这至少会奏效。如果解析器的状态在测试之间保持不变,那么它可能会产生副作用-然后必须重置该状态。如果它是无状态的,就不会有问题。我看得更远了一点,正如@hoefling在他的评论中指出的,这似乎是不可能的。因此,将单体与组合装置一起使用可能是您的最佳选择。有趣的问题,不管怎样。。。