Estoy tratando de evitar repetir demasiadas repeticiones en mis pruebas, y quiero reescribirlas de una manera más estructurada. Digamos que tengo dos analizadores diferentes que pueden analizar un texto en un doc. Ese documento se usaría en otras pruebas. El objetivo final es exponer un accesorio doc() que se pueda usar en otras pruebas, y que esté parametrizado de tal manera que ejecute todas las combinaciones de analizadores y textos dados.

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

La pregunta es, ahora, cómo crear un accesorio doc() que se vería así:

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

Donde parser está parametrizado para ser parser_a y parser_b, y text para ser short_text y long_text. Esto significa que en total doc probaría cuatro combinaciones de analizadores y texto en total.

La documentación sobre los accesorios parametrizados de PyTest es bastante vaga y no pude encontrar una respuesta sobre cómo abordar esto. Toda ayuda bienvenida.

3
Bram Vanroy 28 abr. 2020 a las 15:49

2 respuestas

No estoy seguro de si esto es exactamente lo que necesita, pero podría usar funciones en lugar de dispositivos y combinarlos en dispositivos:

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)

El resultado resultante de pytest es:

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

ACTUALIZACIÓN: Agregué una fábrica de singleton para el analizador como se discutió en los comentarios como ejemplo. Sería bueno si a alguien se le ocurre una solución más limpia.

0
MrBean Bremen 29 abr. 2020 a las 14:05

Su dispositivo debe verse así:

@pytest.fixture(scope='function')
def doc_fixture(request):
    parser = request.param[0]
    text = request.param[1]
    return parser.parse(text)

Y úsalo de la siguiente manera:

@pytest.mark.parametrize('doc_fixture', [parser_1, 'short text'], indirect=True)
def test_sth(doc_fixture):
    ...  # Perform tests
0
Maciej M 28 abr. 2020 a las 13:00