Что касается вашего 2 вопроса - предложенная в комментарии ссылка на руководство , похоже, именно то, что нужно делать. Он позволяет «устанавливать дорогостоящие ресурсы, такие как соединения с БД или подпроцесс, только во время самого теста».
Но что касается 1 вопроса, кажется, что такая функция не реализована. Вы можете напрямую передать генератор на parametrize
следующим образом:
@pytest.mark.parametrize('data', data_gen)
def test_gen(data):
...
Но pytest будет list()
вашего генератора -> Проблемы с ОЗУ сохраняются и здесь.
Я также обнаружил некоторые проблемы с github , которые пролили больше света на , почему pytest не обрабатывает генератор лениво. И это похоже на проблему дизайна. Так что "невозможно правильно управлять параметризацией, имеющей генератор в качестве значения" из-за
"pytest должен собрать все эти тесты со всеми метаданными ...
сбор происходит всегда перед запуском теста ".
Есть также некоторые ссылки на hypothesis
или nose's yield-base tests
в таких случаях. Но если вы все еще хотите придерживаться pytest
, есть несколько обходных путей:
- Если вы каким-то образом знали количество сгенерированных параметров, вы можете сделать следующее:
import pytest
def get_data(N):
for i in range(N):
yield list(range(N))
N = 3000
data_gen = get_data(N)
@pytest.mark.parametrize('ind', range(N))
def test_yield(ind):
data = next(data_gen)
assert data
Итак, здесь вы параметризуете index
(что не так полезно - просто указывает число выполнений, которое нужно выполнить) и генерируете данные внутри следующего запуска.
Вы также можете обернуть его в memory_profiler
:
Results (46.53s):
3000 passed
Filename: run_test.py
Line # Mem usage Increment Line Contents
================================================
5 40.6 MiB 40.6 MiB @profile
6 def to_profile():
7 76.6 MiB 36.1 MiB pytest.main(['test.py'])
И сравните с простым:
@pytest.mark.parametrize('data', data_gen)
def test_yield(data):
assert data
Который «съедает» гораздо больше памяти:
Results (48.11s):
3000 passed
Filename: run_test.py
Line # Mem usage Increment Line Contents
================================================
5 40.7 MiB 40.7 MiB @profile
6 def to_profile():
7 409.3 MiB 368.6 MiB pytest.main(['test.py'])
- Если вы хотите параметризовать свой тест по другим параметрам одновременно, вы можете сделать небольшое обобщение предыдущего предложения, например, так:
data_gen = get_data(N)
@pytest.fixture(scope='module', params=len_of_gen_if_known)
def fix():
huge_data_chunk = next(data_gen)
return huge_data_chunk
@pytest.mark.parametrize('other_param', ['aaa', 'bbb'])
def test_one(fix, other_param):
data = fix
...
Таким образом, мы используем прибор здесь на уровне module
для того, чтобы «предварительно» установить наши данные для параметризованного теста. Обратите внимание, что прямо здесь вы можете добавить еще один тест, и он также будет получать сгенерированные данные. Просто добавьте его после test_two:
@pytest.mark.parametrize('param2', [15, 'asdb', 1j])
def test_two(fix, param2):
data = fix
...
ПРИМЕЧАНИЕ: если вы не знаете количество сгенерированных данных, вы можете использовать этот трюк: задайте приблизительное значение (лучше, если оно будет немного больше, чем количество сгенерированных тестов) и отметьте «пройденные» тесты, если они остановились с StopIteration
что произойдет, когда все данные уже сгенерированы.
Другая возможность - использовать Заводы в качестве приспособлений . Здесь вы встраиваете свой генератор в прибор и try
выход в вашем тесте, пока он не заканчивается. Но есть еще один недостаток - pytest будет рассматривать его как одиночный тест (возможно, с кучей проверок внутри) и потерпит неудачу в случае сбоя одной из сгенерированных данных. Другими словами, если сравнивать с параметризованным подходом, не все статистические данные / функции Pytest могут быть доступны.
И еще одно - использовать pytest.main()
в цикле примерно так:
# data_generate
# set_up test
pytest.main(['test'])
- Не относится к итераторам, а скорее как к способу сэкономить больше времени / оперативной памяти, если один из параметров параметризован:
Просто перенесите некоторую параметризацию в тесты. Пример:
@pytest.mark.parametrize("one", list_1)
@pytest.mark.parametrize("two", list_2)
def test_maybe_convert_objects(self, one, two):
...
Изменить на:
@pytest.mark.parametrize("one", list_1)
def test_maybe_convert_objects(self, one):
for two in list_2:
...
Это похоже на фабрики, но еще проще в реализации. Кроме того, это не только сокращает объем оперативной памяти в несколько раз, но и время для сбора метаинфо. Недостатки здесь - для pytest это будет один тест для всех значений two
. И это работает гладко с «простыми» тестами - если у вас есть какие-то специальные xmark
внутри или что-то может быть проблем.
Я также открыл соответствующую проблему , может появиться дополнительная информация / изменения по этой проблеме.