Python, модульное тестирование и фиктивный импорт

Я участвую в проекте, где мы начинаем рефакторинг некоторой массивной базы кода. Сразу же возникла проблема, заключающаяся в том, что каждый файл импортирует множество других файлов. Как мне элегантно имитировать это в моем модульном тесте, не изменяя фактический код, чтобы я мог начать писать модульные тесты?

В качестве примера: файл с функциями, которые я хочу протестировать, импортирует десять других файлов, которые являются частью нашего программного обеспечения, а не базовыми библиотеками Python.

Я хочу иметь возможность запускать модульные тесты как можно более раздельно, и пока я собираюсь тестировать только функции, которые не зависят от вещей из импортируемых файлов.

Спасибо за ответы на все вопросы.

С самого начала я действительно не знал, чем хочу заниматься, но теперь думаю, что знаю.

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

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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
13
0
4 166
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

"импортирует много других файлов"? Импортирует множество других файлов, которые являются частью вашей настраиваемой базы кода? Или импортирует множество других файлов, которые являются частью дистрибутива 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) и просто импортируете это.

Или же

  • Вы не доверяете чему-то (то есть своему собственному коду) и вы

    1. протестировать отдельно и
    2. имитируйте это для автономного тестирования.

Вы бы протестировали библиотеки Python? Если так, у тебя много работы. Если нет, то вам, возможно, стоит только имитировать то, что вы собираетесь тестировать.

Если вы действительно хотите возиться с механизмом импорта python, взгляните на модуль ihooks. Он предоставляет инструменты для изменения поведения встроенного __import__. Но из вашего вопроса не ясно, зачем вам это нужно.

Никаких сложных манипуляций не требуется, если вы хотите быстро исправить ошибки перед юнит-тестами.

Если модульные тесты находятся в том же файле, что и код, который вы хотите протестировать, просто удалите ненужный модуль из словаря 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()

Спасибо за ответ. Я многому научился. Но на самом деле я хочу сделать наоборот. Тесты находятся в другом файле, и когда я импортирую файл, содержащий функции, которые я хочу протестировать, я хочу, чтобы этот файл думал, что все остальные файлы уже импортированы.

Rickard Lindroth 07.10.2008 18:59

В своем комментарии над вы говорите, что хотите убедить 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, чтобы избежать выхода из системы с обернутым импортом вместо импорта по умолчанию в случае ошибки

Yonatan 19.02.2013 12:37

Другие вопросы по теме