Python, юнит-тестирование и импровизированный импорт - PullRequest
12 голосов
/ 07 октября 2008

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

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

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

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

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

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

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

Ответы [ 5 ]

7 голосов
/ 07 октября 2008

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

Этот пример может быть дополнительно изменен, например, на импортировать другой модуль, чем нужно, вместо того, чтобы просто не импортировать его, или даже подделать объект модуля каким-либо собственным объектом.

1 голос
/ 07 октября 2008

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

1 голос
/ 07 октября 2008

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

Если ваш импорт не работает, у вас есть «простая» проблема PYTHONPAT H. Получите все ваши различные каталоги проектов на 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? Если так, у тебя много работы. Если нет, то, возможно, вам стоит только высмеивать то, что вы на самом деле собираетесь проверить.

0 голосов
/ 07 октября 2008

В своем комментарии выше вы говорите, что хотите убедить python в том, что некоторые модули уже импортированы. Это все еще кажется странной целью, но если это действительно то, что вы хотите сделать, в принципе, вы можете прокрасться за спиной механизма импорта и изменить sys.modules. Не уверен, как это будет работать для импорта пакетов, но должно подойти для абсолютного импорта.

0 голосов
/ 07 октября 2008

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

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