Сочетание py.test и trio/curio

Я хотел бы объединить pytest и trio (или curio, если так проще), то есть написать свои тесты в качестве функций сопрограммы. Этого относительно легко достичь, объявив пользовательский бегун в conftest.py:

    @pytest.mark.tryfirst
    def pytest_pyfunc_call(pyfuncitem):
        '''If item is a coroutine function, run it under trio'''

        if not inspect.iscoroutinefunction(pyfuncitem.obj):
            return

        kernel = trio.Kernel()
        funcargs = pyfuncitem.funcargs
        testargs = {arg: funcargs[arg]
                    for arg in pyfuncitem._fixtureinfo.argnames}
        try:
            kernel.run(functools.partial(pyfuncitem.obj, **testargs))
        finally:
            kernel.run(shutdown=True)

        return True

Это позволяет мне писать контрольные примеры вот так:

    async def test_something():
        server = MockServer()
        server_task = await trio.run(server.serve)
        try:
             # test the server
        finally:
             server.please_terminate()
             try:
                 with trio.fail_after(30):
                     server_task.join()
             except TooSlowError:
                 server_task.cancel()

Но это много шаблонного. В не асинхронном коде я бы выделил это в фиксатор:

@pytest.yield_fixture()
def mock_server():
    server = MockServer()
    thread = threading.Thread(server.serve)
    thread.start()

    try:
        yield server
    finally:
        server.please_terminate()
        thread.join()
        server.server_close()

def test_something(mock_server):
   # do the test..

Есть ли способ сделать то же самое в трио, то есть реализовать асинхронные приборы? В идеале я бы просто написал:

async def test_something(mock_server):
   # do the test..

1 ответ

Решение

Изменить: ответ ниже в основном не имеет значения сейчас - вместо этого используйте pytest-trio и следуйте инструкциям в его руководстве.


Ваш пример pytest_pyfunc_call код не работает, потому что это смесь трио и любопытства:-). Для трио есть декоратор trio.testing.trio_test это можно использовать для маркировки отдельных тестов (например, если вы использовали классический юнит-тест или что-то в этом роде), поэтому самый простой способ написать функцию плагина pytest - просто применить это к каждому асинхронному тесту:

from trio.testing import trio_test

@pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
    if inspect.iscoroutine(pyfuncitem.obj):
        # Apply the @trio_test decorator
        pyfuncitem.obj = trio_test(pyfuncitem.obj)

Если вам интересно, это в основном эквивалентно:

import trio
from functools import wraps, partial

@pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
    if inspect.iscoroutine(pyfuncitem.obj):
        fn = pyfuncitem.obj
        @wraps(fn)
        def wrapper(**kwargs):
            trio.run(partial(fn, **kwargs))
        pyfuncitem.obj = wrapper

В любом случае, это не решит вашу проблему с приборами - для этого вам нужно что-то гораздо более сложное.

Другие вопросы по тегам