Как использовать косвенную параметризацию, когда приборы имеют одинаковые параметры?

#python #pytest

#python #pytest

Вопрос:

Я использую pytest косвенную параметризацию для параметризации восходящего устройства. Для меня это работает нормально.

Однако теперь я застрял, когда вышестоящие приборы имеют одинаковые имена аргументов, и я хочу передать им разные значения.

Как я могу использовать косвенную параметризацию, когда аргументы вышестоящего устройства, подлежащего параметризации, имеют одинаковые имена?


Пример кода

 import pytest

class Config:
    """This is a configuration object."""
    def __init__(self, a: int, b: int):
        self._a = a
        self._b = b

@pytest.fixture
def config(a: int, b: int) -> Config:
    return Config(a, b)

class Foo:
    def __init__(self, config: Config):
        """This does some behavior."""
        self.config = config

@pytest.fixture
def foo(config: Config) -> Foo:
    return Foo(config)

class Bar:
    def __init__(self, config: Config):
        """This does some other behavior."""
        self.config = config

@pytest.fixture
def bar(config: Config) -> Bar:
    return Bar(config)

class TestFooBarTogether:
    @pytest.mark.parametrize("a, b", [(1, 2)])
    def test_foo_alone(self, foo: Foo) -> None:
        """This works fine, since parameters get passed all the way to Config."""

    @pytest.mark.parametrize("a, b", [(1, 2)])
    def test_together(self, foo: Foo, bar: Bar) -> None:
        """Test interactions between the two objects.

        This does not work, because both foo and bar get the same config.
        How can I pass a different config to foo and bar, even though Config
        takes both an "a" and a "b"?
        
        I would like to pass `foo` something like (1, 2) and 
        `bar` something like (3, 4).

        """
  

Ответ №1:

Обновить

Я не вижу возможности использовать config прибор при параметризации обоих foo и bar с разными Config экземплярами. Отбрасывая config прибор и параметризируя foo и bar косвенно, я прихожу к:

 @pytest.fixture
def foo(request) -> Foo:
    return Foo(Config(*request.param))


@pytest.fixture
def bar(request) -> Bar:
    return Bar(Config(*request.param))


@pytest.mark.parametrize("foo", [(1, 2)], indirect=True)
def test_foo_alone(foo: Foo) -> None:
    assert foo.config._a == 1
    assert foo.config._b == 2


@pytest.mark.parametrize("foo,bar", [((1, 2), (3, 4))], indirect=True)
def test_together(foo: Foo, bar: Bar) -> None:
    assert foo.config._a == 1
    assert foo.config._b == 2
    assert bar.config._a == 3
    assert bar.config._b == 4
  

Ожидаемое config a и b параметризованное приспособление здесь все равно не подойдет, IMO, поскольку ожидается, что оно будет вызываться new при каждом вызове, как обычная функция — вместо этого я просто использую Config конструктор.

Оригинальный ответ

Вы всегда можете переключиться на заводские приспособления, если вам нужно обеспечить новые объекты при каждом config вызове. Аргументы a и b инкапсулируются на заводе, поэтому не должно быть необходимости перетаскивать их.

 ConfigFactory = Callable[[], Config]

@pytest.fixture
def config(a: int, b: int) -> ConfigFactory:
    return lambda: Config(a, b)


@pytest.fixture
def foo(config: ConfigFactory) -> Foo:
    return Foo(config())


@pytest.fixture
def bar(config: ConfigFactory) -> Bar:
    return Bar(config())


@pytest.mark.parametrize("a, b", [(1, 2)])
def test_together(foo: Foo, bar: Bar) -> None:
    assert not foo.config == bar.config
  

Комментарии:

1. Спасибо @hoefling, это почти работает. Однако это еще не совсем так. Я хочу предоставить что-то вроде (1, 2) to foo и (3, 4) to bar . Другими словами, фабрика успешно создает экземпляры разных Config объектов (мы на полпути), но у них одинаковые переданные аргументы ( a, b ) . Есть ли какой-нибудь способ передать другой набор значений в bar ‘s ConfigFactory ? Я также обновил вопрос, чтобы сделать это более понятным.

2. @IntrastellarExplorer Теперь я понимаю, что вы имеете в виду — действительно, это сложно. Я обновил ответ предложением по измененным требованиям, но он config полностью устраняет приспособление. Может быть, кто-то придумает лучшее предложение.

3. Еще раз спасибо @hoefling. Я закончил тем, что делал подобное несколько недель назад после публикации этого вопроса. Однако я не думал об использовании этого request.param метода.

Ответ №2:

Возможным обходным путем может быть создание нового приспособления, которое принимает две разные конфигурации. Вот два способа сделать это.

 @pytest.fixture
def foo_or_bar(config1: Config, config2: Config):
    return Foo(config1), Bar(config2)


class TestFooBarTogether:
    @pytest.mark.parametrize("a, b", [(1, 2)])
    def test_foo_alone(self, foo: Foo) -> None:
        """This works fine, since parameters get passed all the way to Config."""

    @pytest.mark.parametrize("config1, config2", [(Config(1, 2), Config(3, 4))])
    def test_together(self, foo_or_bar) -> None:
        """Test interactions between the two objects.

        """
        (foo, bar) = foo_or_bar
        assert foo.config._a == 1
        assert foo.config._b == 2
        assert bar.config._a == 3
        assert bar.config._b == 4
  

Или

 @pytest.fixture
def foo_or_bar(a_b, c_d):
    (a, b) = a_b
    (c,d) = c_d
    
    config1 = Config(a,b)
    config2 = Config(c,d)

    return Foo(config1), Bar(config2)


class TestFooBarTogether:
    @pytest.mark.parametrize("a, b", [(1, 2)])
    def test_foo_alone(self, foo: Foo) -> None:
        """This works fine, since parameters get passed all the way to Config."""
        assert isinstance(foo.config, Config)

    @pytest.mark.parametrize("a_b, c_d", [((1, 2), (3, 4))])
    def test_together(self, foo_or_bar) -> None:
        """Test interactions between the two objects.

        This does not work, because both foo and bar get the same config.
        How can I pass a different config to foo and bar, even though Config 
        takes both an "a" and a "b"??

        """
        (foo, bar) = foo_or_bar
        assert foo.config._a == 1
        assert foo.config._b == 2
        assert bar.config._a == 3
        assert bar.config._b == 4