Как передать параметризованное устройство в качестве параметра другому устройству - PullRequest
3 голосов
/ 28 апреля 2020

Я стараюсь не повторять слишком много шаблонов в моих тестах и ​​хочу переписать их более структурированным образом. Допустим, у меня есть два разных анализатора, которые оба могут анализировать текст в doc. Это do c будет использоваться в других тестах. Конечная цель состоит в том, чтобы выставить прибор 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 довольно расплывчата, и я не смог найти ответ на этот вопрос. Вся помощь приветствуется.

Ответы [ 2 ]

1 голос
/ 28 апреля 2020

Ваш прибор должен выглядеть следующим образом:

@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') == ''
0 голосов
/ 28 апреля 2020

Не уверен, что это именно то, что вам нужно, но вы можете просто использовать функции вместо приборов и объединить их в приборах:

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)

В результате вы получите:

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

ОБНОВЛЕНИЕ: Я добавил фабрику синглтонов для парсера, как обсуждалось в комментариях в качестве примера.

ПРИМЕЧАНИЕ: Я пытался использовать pytest.lazy_fixture, как предложено @hoefling. Это работает и позволяет передавать парсер и текст непосредственно из прибора, но я не мог заставить его (пока) работать так, чтобы каждый парсер создавался только один раз. Для справки вот измененный код при использовании 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)
...