【问题标题】:How to pass a parameterised fixture as a parameter to another fixture如何将参数化的夹具作为参数传递给另一个夹具
【发布时间】:2020-04-28 12:49:06
【问题描述】:

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

@pytest.fixture
def parser_a():
    return "parser_a"  # actually a parser object

@pytest.fixture
def parser_b():
    return "parser_b"  # actually a parser object

@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."

现在的问题是,如何创建一个看起来像这样的doc() 固定装置:

@pytest.fixture(params=???)
def doc(parser, text):
    return parser.parse(text)

其中parser 参数化为parser_a 和parser_b,text 参数化为short_text 和long_text。这意味着doc 总共将测试四种解析器和文本组合。

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

【问题讨论】:

标签: python pytest fixtures parameterized-unit-test


【解决方案1】:

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

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

更新: 我为解析器添加了一个单例工厂,如 cmets 中所讨论的示例。

注意: 我尝试按照@hoefling 的建议使用pytest.lazy_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)

【讨论】:

  • 感谢您的回复。这是否意味着解析器将被一遍又一遍地创建,因为它们不在固定装置中?
  • 是的 - 如果不需要,这还不够……我稍后再看看。
  • 我通过使用全局 dict PARSERS 绕过了这个问题,在您的示例中,def parser_a() 变为:if 'parser_a' not in PARSERS: PARSERS['parser_a'] = Parser('parser_a'); return PARSERS['parser_a']。这似乎有效,但我不确定它是否有任何警告。
  • 是的,这总是有可能的——让它们基本上是单身。我还没有想出另一个解决方案,这至少会起作用。如果解析器的状态在测试之间保持不变,它可能会产生副作用 - 然后必须将其重置。如果是无状态的,就没有问题了。
  • 我看了一点,正如@hoefling 在他的评论中指出的那样,这似乎是不可能的。因此,将单件与组合夹具一起使用可能是您最好的选择。无论如何,有趣的问题......
【解决方案2】:

您的灯具应如下所示:

@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

您可以使用 pytest.mark.parametrize 混合和匹配参数组合

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

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

还有一个示例运行结果(如预期的那样测试失败):

=========================================================================================== short test summary info ============================================================================================
FAILED ../../tmp/::test_something[doc_fixture0] - AssertionError: assert Namespace(one='parserA', two='ShortText') == ''
FAILED ../../tmp/::test_something[doc_fixture1] - AssertionError: assert Namespace(one='parserA', two='LongText') == ''
FAILED ../../tmp/::test_something[doc_fixture2] - AssertionError: assert Namespace(one='parserB', two='ShortText') == ''
FAILED ../../tmp/::test_something[doc_fixture3] - AssertionError: assert Namespace(one='parserB', two='LongText') == ''

【讨论】:

  • 您能否在您的帖子中用文字解释应该如何阅读 parameterize 行(参数之间的关系)。
  • fixture的名字,参数列表,indirect=True指定params发送给fixtrue。
  • 我不认为这是我想要的。如果我理解正确,您只需将 parser_a 和 short_text 发送到夹具,但我需要对解析器和文本的所有组合执行此操作。
  • @BramVanroy 您可以混合和匹配pytest.mark.parametrize 的组合编辑答案以添加更合适的示例。
  • @salparadise 但是现在它只显示了一个级别的灯具,对吧? “'ShortText'”和“parserA”不只是字符串吗?我需要它们自己成为固定装置(或者,正如 MrBean 的回答中所播种的那样,通过使用单例)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-05
  • 1970-01-01
  • 2013-08-03
  • 1970-01-01
  • 2020-11-13
相关资源
最近更新 更多