Перезапустить python (или перезагрузить модули) в тестах py.test

У меня есть пакет (python3), который имеет совершенно другое поведение в зависимости от того, как он init() ed (возможно, не лучший дизайн, но переписывание не является вариантом). Модуль может быть только init() ed один раз, второй раз дает ошибку. Я хочу протестировать этот пакет (оба поведения) с помощью py.test.

Примечание: природа пакета делает оба поведения взаимоисключающими, нет никакой возможной причины когда-либо хотеть как в отдельной программе.

В моем тестовом каталоге есть серверные test_xxx.py модули. Каждый модуль будет инициализировать пакет на нужном уровне (используя приспособления). Поскольку py.test запускает интерпретатор python один раз, запуск всех тестовых модулей в одном запуске py.test не выполняется.

Обезглавить пакет, чтобы второй init() не был чем-то, что я хочу сделать, поскольку есть внутреннее кэширование и т.д., что может привести к необъяснимому поведению.

  • Можно ли сказать py.test запускать каждый тестовый модуль в отдельном процессе python (тем самым не влияя на inits в другом тестовом модуле).
  • Есть ли способ надежно перезагрузить пакет (включая все подзависимости и т.д.)?
  • Есть ли другое решение (я собираюсь импортировать, а затем не импортировать пакет в приспособление, но это кажется чрезмерным)?

Ответ 1

Чтобы перезагрузить модуль, попробуйте использовать reload() из библиотеки imp

Пример:

from imp import reload
import some_lib
#do something
reload(some_lib) 

Кроме того, запуск каждого теста в новом процессе является жизнеспособным, но многопроцессорный код является болезненным для отладки.

Пример

import some_test
from multiprocessing import Manager, Process

#create new return value holder, in this case a list
manager = Manager()
return_value = manager.list()
#create new process
process =  Process(target=some_test.some_function, args=(arg, return_value))
#execute process
process.start()
#finish and return process
process.join()
#you can now use your return value as if it were a normal list, 
#as long as it was assigned in your subprocess

Ответ 2

У меня та же проблема, и я нашел три решения:

  1. перезагрузить (some_lib)
  2. патч SUT, так как импортируемый метод является ключом и значением в SUT, вы можете SUT. Например, если вы используете f2 m2 в m1, вы можете исправить m1.f2 вместо m2.f2
  3. импортировать модуль и использовать module.function.

Ответ 3

Когда-то у меня была похожая проблема, но довольно плохой дизайн..

@pytest.fixture()
def module_type1():
    mod = importlib.import_module('example')
    mod._init(10)
    yield mod
    del sys.modules['example']

@pytest.fixture()
def module_type2():
    mod = importlib.import_module('example')
    mod._init(20)
    yield mod
    del sys.modules['example']


def test1(module_type1)
    pass

def test2(module_type2)
    pass

В примере /init.py было что-то вроде этого

def _init(val):
    if 'sample' in globals():
        logger.info(f'example already imported, val{sample}' )
    else:
        globals()['sample'] = val
        logger.info(f'importing example with val : {val}')

выход:

importing example with val : 10
importing example with val : 20

Не знаю, насколько сложен ваш пакет, но если это просто глобальные переменные, то это, вероятно, поможет.