Написание тестовых наборов pytest для asyncio с потоками

#python-asyncio #stdout #pytest-asyncio #nest-asyncio

Вопрос:

Я пытаюсь написать тестовый набор pytest для функции asyncio, которая считывает выходные потоки (stderr/stdout) и изменяет строки. Функция, которую я хочу протестировать (которая снова вызывается внутри asyncio.gather ), как показано ниже:

 import asyncio

async def watch(stream):

    while True:
        lines = await stream.read(2**16)
        if not lines or lines == "":
            break

        lines = lines.strip().split("n")
        for line in lines:
            print(f'myPrefix-{line}')
 

Тестовый кейс pytest, который я написал, выглядит следующим образом:

 import asyncio
from io import StringIO
import pytest

@pytest.fixture(autouse=True)
def event_loop():
    loop = asyncio.get_event_loop()
    yield loop
    loop.close()

@pytest.mark.asyncio
async def test_watch(event_loop):
    expected_outcome = "myPrefix-This is stdout"

    def write_something():
        print("This is stdout")

    with patch("sys.stdout", new=StringIO()) as mock_stdout:
        write_something()
        output = await watch(mock_stdout.getvalue())
        assert output.return_value == expected_outcome
 

Однако, когда я выполняю этот тест, я сталкиваюсь AttributeError: 'str' object has no attribute 'read' с этим . Как протестировать сопрограммы asyncio при работе с потоками stdout/stderr?

Ответ №1:

StringIO не имеет методов сопрограммы для read , поэтому вы не можете издеваться над этим и заставить его работать с функцией сопрограммы watch (вызов getvalue StringIO экземпляра также просто передает строку, записанную в stdout, что объясняет полученную ошибку). Предполагая , что поток в вашей функции наблюдения является экземпляром StreamReader , вы можете просто создать экземпляр asyncio StreamReader в своем тесте и использовать feed_data метод для записи чего-либо в поток. Затем вы можете передать это watch кому-нибудь . Затем вы можете использовать capsys приспособление, входящее в состав Pytest, для записи того, что watch записывается в stdout.

Ниже приведена обновленная версия вашего кода, которая проходит как отдельная:

 import asyncio
import pytest


async def watch(stream):
    while True:
        lines = await stream.read(2 ** 16)
        if not lines or lines == "":
            break

        lines = lines.decode().strip().split("n") #note the use of decode()
        for line in lines:
            print(f'myPrefix-{line}')


@pytest.fixture(autouse=True)
def event_loop():
    loop = asyncio.get_event_loop()
    yield loop
    loop.close()


@pytest.mark.asyncio
async def test_watch(capsys):
    expected_outcome = "myPrefix-This is stdoutn"

    stream = asyncio.StreamReader()
    stream.feed_data(b'This is stdoutn')
    stream.feed_eof()

    await watch(stream)
    captured = capsys.readouterr()
    assert captured.out == expected_outcome