Проводить тесты одновременно - PullRequest
4 голосов
/ 04 июня 2019

Я хотел бы запустить несколько тестов одновременно, используя asyncio (/ curio / trio) и pytest, но я не смог найти никакой информации об этом. Я должен запланировать их сам? И если я это сделаю, есть ли способ получить хороший вывод, который разделяет (под) тестовые случаи?

Вот небольшой пример игрушки, для которого я пробую это:

import pytest
import time
import asyncio

pytestmark = pytest.mark.asyncio
expected_duration = 1
accepted_error = 0.1

async def test_sleep():
    start = time.time()
    time.sleep(expected_duration)
    duration = time.time() - start
    assert abs(duration-expected_duration) < accepted_error

async def test_async_sleep():
    start = time.time()
    await asyncio.sleep(expected_duration)
    duration = time.time() - start
    assert abs(duration-expected_duration) < accepted_error

Ответы [ 2 ]

3 голосов
/ 05 июня 2019

Использование pytest-подтестов, предложенных Натаниэлем, кажется жизнеспособным решением.Вот как это можно решить с помощью трио: он запускает подтесты для каждой функции, имя которой начинается с io_.

import pytest
import sys
import trio
import inspect
import re
import time


pytestmark = pytest.mark.trio
io_test_pattern = re.compile("io_.*")


async def tests(subtests):

    def find_io_tests(subtests, ignored_names):
        functions = inspect.getmembers(sys.modules[__name__], inspect.isfunction)
        for (f_name, function) in functions:
            if f_name in ignored_names:
                continue
            if re.search(io_test_pattern, f_name):
                yield (run, subtests, f_name, function)

    async def run(subtests, test_name, test_function):
        with subtests.test(msg=test_name):
            await test_function()

    self_name = inspect.currentframe().f_code.co_name
    async with trio.open_nursery() as nursery:
        for io_test in find_io_tests(subtests, {self_name}):
            nursery.start_soon(*io_test)


accepted_error = 0.1

async def io_test_1():
    await assert_sleep_duration_ok(1)

async def io_test_2():
    await assert_sleep_duration_ok(2)

async def io_test_3():
    await assert_sleep_duration_ok(3)

async def io_test_4():
    await assert_sleep_duration_ok(4)

async def assert_sleep_duration_ok(duration):
    start = time.time()
    await trio.sleep(duration)
    actual_duration = time.time() - start
    assert abs(actual_duration - duration) < accepted_error

Запуск python -m pytest -v выводов:

============================ test session starts =============================
platform darwin -- Python 3.7.0, pytest-4.6.2, py-1.8.0, pluggy-0.12.0
plugins: asyncio-0.10.0, trio-0.5.2, subtests-0.2.1
collected 1 item

tests/stripe_test.py::tests PASSED                                     [100%]
tests/stripe_test.py::tests PASSED                                     [100%]
tests/stripe_test.py::tests PASSED                                     [100%]
tests/stripe_test.py::tests PASSED                                     [100%]
tests/stripe_test.py::tests PASSED                                     [100%]

========================== 1 passed in 4.07 seconds ==========================

Это не такидеально, так как процент только относительно количества тестов, а не количества подтестов (т. е. io_* помеченных здесь функций), но это кажется хорошим началом.

Также обратите внимание, что используется time.time(), так что имеет смысл как для trio, так и для asyncio, но в реальном случае вместо этого следует использовать trio.current_time().

Те же самые тесты могут быть выполнены с использованиемasyncio, вам в основном придется заменить три вещи:

  • pytestmark = pytest.mark.triopytestmark = pytest.mark.asyncio
  • yield (run, subtests, f_name, function)yield run(subtests, f_name, function)
  • И, наконец, детскуюцикл должен быть заменен чем-то вроде:
await asyncio.gather(*find_io_tests(subtests, {self_name}))
3 голосов
/ 05 июня 2019

К сожалению, внутренняя работа pytest невозможна, и вы не можете одновременно запустить несколько тестов под одним и тем же вызовом trio.run / asyncio.run / curio.run. (Это также хорошо в некоторых отношениях - это предотвращает утечку состояния между тестами, и, по крайней мере, с трио позволяет по-разному настраивать трио для разных тестов, например, для настройки одного теста на использование autojump clock во время другого теста не делает.)

Определенно, самый простой вариант - использовать pytest-xdist для запуска тестов в отдельных потоках. Вы все еще можете использовать асинхронно внутри каждого теста - все эти асинхронные библиотеки поддерживают запуск разных циклов в разных потоках.

Если вам действительно нужно использовать асинхронный параллелизм, то я думаю, что вам придется написать одну тестовую функцию pytest, а затем внутри этой функции выполнить свое собственное планирование и параллелизм. Если вы делаете это таким образом, то с точки зрения pytest есть только один тест, поэтому получить хороший результат для каждого теста будет непросто. Я думаю, вы могли бы попробовать использовать pytest-subtests ?

...