Возврат нескольких объектов из прибора Pytest

Я учусь тому, как использовать pytest, тестируя простую реализацию генератора событий.

В основном это выглядит так

class EventEmitter():
    def __init__(self):
        ...
    def subscribe(self, event_map):
        # adds listeners to provided in event_map events
    def emit(self, event, *args):
        # emits event with given args

Для удобства я создал класс Listener который используется в тестах

class Listener():
    def __init__(self):
        ...
    def operation(self):
        # actual listener

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

@pytest.fixture
def event():
    ee = EventEmitter()
    lstr = Listener()
    ee.subscribe({"event" : [lstr.operation]})
    return lstr, ee

def test_emitter(event):
    lstr = event[0]
    ee = event[1]
    ee.emit("event")
    assert lstr.result == 7 # for example

Чтобы протестировать источник событий, мне нужно проверить, изменилось ли внутреннее состояние слушателя после распространения события. Таким образом, мне нужны два объекта, и мне интересно, есть ли лучший способ сделать это (возможно, использовать два прибора вместо одного как-нибудь), потому что это выглядит мне некрасиво.

Ответ 1

Да! Вероятно, вам понадобится два светильника в этом случае.

вы можете попробовать @pytest.yield_fixture как:

@pytest.yield_fixture
def event():
    ...
    yield <event_properties>

@pytest.yield_fixture
def listener(event):
    ...
    yield <listener_properties>

Ответ 2

Обычно, чтобы избежать tuples и украшать ваш код, вы можете объединить их вместе с одним модулем в качестве класса, который был сделан для вас, используя collections.namedtuple:

import collections
EventListener = collections.namedtuple('EventListener', 'event listener')

Теперь измените ваше приспособление:

@pytest.fixture
def event_listener():
 e = EventListener(EventEmitter(), Listener())
 e.event.subscribe({'event' : [e.listener.operation]})
 return e

Теперь измените свой тест:

def test_emitter(event_listener):
 event_listener.event.emit('event')
 assert event_listener.listener.result == 7

Ответ 3

Вы должны использовать функцию Python, называемую итеративную распаковку в переменные.

def test_emitter(event):
    lstr, ee = event # unpacking
    ee.emit("event")
    assert lstr.result == 7

По сути, вы назначаете event[0] для lstr, а event[1] для ee. Использование этой функции - очень элегантный способ избежать использования индексов.

Отбросив

Если вы собираетесь использовать свой прибор в множественных тестах и вам не нужны все значения в каждом тесте, вы также можете отказаться от некоторых элементов итерируемого, если вы не заинтересованы в их использовании следующим образом:

l = ['a', 'b', 'c', 'd']
a, b, c, d = l # unpacking all elements
a, _, c, d = l # discarding b
a, _, _, d = l # python 2: discard b and c
a, *_, d = l # python 3: discard b and c
a, _, _, _ = l # python2: discard, b, c and d
a, *_ = l # python3: discard b, c, and d

Теоретически, вы буквально не отбрасываете значения, но в Python _, так называемое "мне все равно", используется для игнорирования определенных значений.

Ответ 4

Если вы не можете позволить себе легко разделить свое устройство кортежа на два независимых устройства, теперь вы можете "распаковать" устройство кортежа или списка в другие устройства, используя мой pytest-cases как описано в этом ответе.

Для вашего примера это будет выглядеть так:

from pytest_cases import pytest_fixture_plus

@pytest_fixture_plus(unpack_into="lstr,ee")
def event():
    ee = EventEmitter()
    lstr = Listener()
    ee.subscribe({"event" : [lstr.operation]})
    return lstr, ee

def test_emitter(lstr, ee):
    ee.emit("event")
    assert lstr.result == 7 # for example