Я участвую в проекте, где мы начинаем рефакторинг некоторой массивной базы кода. Сразу же возникла проблема, заключающаяся в том, что каждый файл импортирует множество других файлов. Как мне элегантно имитировать это в моем модульном тесте, не изменяя фактический код, чтобы я мог начать писать модульные тесты?
В качестве примера: файл с функциями, которые я хочу протестировать, импортирует десять других файлов, которые являются частью нашего программного обеспечения, а не базовыми библиотеками Python.
Я хочу иметь возможность запускать модульные тесты как можно более раздельно, и пока я собираюсь тестировать только функции, которые не зависят от вещей из импортируемых файлов.
Спасибо за ответы на все вопросы.
С самого начала я действительно не знал, чем хочу заниматься, но теперь думаю, что знаю.
Проблема заключалась в том, что некоторый импорт был возможен только тогда, когда все приложение работало из-за некоторой сторонней автоматической магии. Поэтому мне пришлось сделать несколько заглушек для этих модулей в каталоге, который я указал с помощью sys.path
Теперь я могу импортировать файл, содержащий функции, для которых я хочу написать тесты в моем файле модульного теста, без жалоб на отсутствующие модули.






"импортирует много других файлов"? Импортирует множество других файлов, которые являются частью вашей настраиваемой базы кода? Или импортирует множество других файлов, которые являются частью дистрибутива Python? Или импортирует множество других файлов проектов с открытым исходным кодом?
Если ваш импорт не работает, у вас "простая" проблема с PYTHONPATH. Поместите все ваши различные каталоги проектов на PYTHONPATH, который вы можете использовать для тестирования. Путь у нас довольно сложный, в Windows мы ведем его так
@set Part1=c:\blah\blah\blah
@set Part2=c:\some\other\path
@set that=g:\shared\stuff
set PYTHONPATH=%part1%;%part2%;%that%
Мы держим каждый отрезок пути отдельно, чтобы мы (а) знали, откуда берутся вещи, и (б) можем управлять изменениями, когда мы перемещаем вещи.
Поскольку PYTHONPATH ищется по порядку, мы можем контролировать, что будет использоваться, изменяя порядок на пути.
Когда у вас есть «все», это становится вопросом доверия.
Либо
Или же
Вы не доверяете чему-то (то есть своему собственному коду) и вы
Вы бы протестировали библиотеки Python? Если так, у тебя много работы. Если нет, то вам, возможно, стоит только имитировать то, что вы собираетесь тестировать.
Никаких сложных манипуляций не требуется, если вы хотите быстро исправить ошибки перед юнит-тестами.
Если модульные тесты находятся в том же файле, что и код, который вы хотите протестировать, просто удалите ненужный модуль из словаря globals().
Вот довольно длинный пример: предположим, у вас есть модуль impp.py с содержимым:
value = 5
Теперь в вашем тестовом файле вы можете написать:
>>> import impp
>>> print globals().keys()
>>> def printVal():
>>> print impp.value
['printVal', '__builtins__', '__file__', 'impp', '__name__', '__doc__']
Обратите внимание, что impp входит в число глобальных, потому что он был импортирован. Вызов функции printVal, использующей модуль impp, по-прежнему работает:
>>> printVal()
5
Но теперь, если вы удалите ключ impp из globals() ...
>>> del globals()['impp']
>>> print globals().keys()
['printVal', '__builtins__', '__file__', '__name__', '__doc__']
... и попробуйте вызвать в printVal(), вы получите:
>>> printVal()
Traceback (most recent call last):
File "test_imp.py", line 13, in <module>
printVal()
File "test_imp.py", line 5, in printVal
print impp.value
NameError: global name 'impp' is not defined
... что, вероятно, именно то, чего вы пытаетесь достичь.
Чтобы использовать его в своих модульных тестах, вы можете удалить глобальные объекты непосредственно перед запуском набора тестов, например в __main__:
if __name__ == '__main__':
del globals()['impp']
unittest.main()
В своем комментарии над вы говорите, что хотите убедить python, что определенные модули уже были импортированы. Это все еще кажется странной целью, но если это действительно то, что вы хотите сделать, в принципе вы можете прокрасться за спиной механизма импорта и изменить sys.modules. Не уверен, как это будет работать для импорта пакетов, но должно подойти для абсолютного импорта.
Если вы хотите импортировать модуль и в то же время убедиться, что он ничего не импортирует, вы можете заменить встроенную функцию __import__.
Например, используйте этот класс:
class ImportWrapper(object):
def __init__(self, real_import):
self.real_import = real_import
def wrapper(self, wantedModules):
def inner(moduleName, *args, **kwargs):
if moduleName in wantedModules:
print "IMPORTING MODULE", moduleName
self.real_import(*args, **kwargs)
else:
print "NOT IMPORTING MODULE", moduleName
return inner
def mock_import(self, moduleName, wantedModules):
__builtins__.__import__ = self.wrapper(wantedModules)
try:
__import__(moduleName, globals(), locals(), [], -1)
finally:
__builtins__.__import__ = self.real_import
И в вашем тестовом коде вместо import myModule напишите:
wrapper = ImportWrapper(__import__)
wrapper.mock_import('myModule', [])
Второй аргумент mock_import - это список имен модулей, которые вы делать хотите импортировать во внутренний модуль.
Этот пример можно дополнительно изменить, например, импортировать другой модуль, чем требуется, вместо того, чтобы просто не импортировать его, или даже издеваться над объектом модуля с помощью какого-то собственного настраиваемого объекта.
Вы хотите добавить try: finally: в метод mock_import, чтобы избежать выхода из системы с обернутым импортом вместо импорта по умолчанию в случае ошибки
Спасибо за ответ. Я многому научился. Но на самом деле я хочу сделать наоборот. Тесты находятся в другом файле, и когда я импортирую файл, содержащий функции, которые я хочу протестировать, я хочу, чтобы этот файл думал, что все остальные файлы уже импортированы.