Сочетание 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
В любом случае, это не решит вашу проблему с приборами - для этого вам нужно что-то гораздо более сложное.