Фиксация модуля Nose-doctest перед импортом модуля

Я использую нос для тестовой коллекции, и я также хочу использовать его плагин doctest. У меня есть модуль, который нуждается в приспособлении, чтобы его можно было импортировать. Поэтому я не могу использовать фикстуры модуля Nose, так как они загружаются из тестируемого модуля. Есть ли способ указать фикстуры модуля для Nose-doctest вне модуля?

Для некоторых вариантов использования можно было бы обнаружить, что выполняется под doctest, и применить фикстуру в начале модуля. Мне было бы интересно услышать ответы и для этого варианта использования.

Однако бывают ситуации, когда это не работает: когда импорт завершается ошибкой из-за SyntaxError, код модуля никогда не запускается. В моем случае я в основном разрабатываю код, совместимый как с Python 2, так и с Python 3 (без 2to3). Однако есть несколько модулей, специфичных для python 3, которые просто не следует проверять носом, когда они работают под python 2. Каков был бы мой лучший вариант здесь?

EDIT: MWE (для ситуации SyntaxError)

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

~/pckg/
  __init__.py
  py3only.py
  ... (other modules)
  tests/
    test_py3only.py

Некоторые тесты написаны как unittest.TestCase, но я также хочу протестировать примеры кода в строках документации. ~/pckg/__init__.py пусто.

~/pckg/py3only.py:

def fancy_py3_func(a:"A function argument annotation (python 3 only syntax)"):
    """ A function using fancy syntax doubling it's input.

    >>> fancy_py3_func(4)
    8
    """
    return a*2

~/pckg/тесты/test_py3only.py:

import sys, unittest

def setup_module():
    if sys.version_info[0] < 3:
        raise unittest.SkipTest("py3only unavailable on python "+sys.version)

class TestFancyFunc(unittest.TestCase):
    def test_bruteforce(self):
        from pckg.py3only import fancy_py3_func
        for k in range(10):
            self.assertEqual(fancy_py3_func(k),2*k)

Тестирование на python 3, все тестируется и проходит (запускается из прилагаемой папки, например ~):

~ nosetests3 -v --with-doctest pckg
Doctest: pckg.py3only.fancy_py3_func ... ok
test_bruteforce (test_py3only.TestFancyFunc) ... ok

В Python 2 фикстура модуля ~/pckg/tests/test_py2only.py правильно определяет ситуацию и пропускает тест. Однако мы получаем SyntaxError из ~/pckg/py3only.py:

~ nosetests -v --with-doctest pckg 
Failure: SyntaxError (invalid syntax (py3only.py, line 1)) ... ERROR
SKIP: py3only unavailable on python 2.7.6 (default, Mar 22 2014, 22:59:56)

Функция, похожая на ~/pckg/tests/test_py3only.py:setup_module(), могла бы решить эту проблему, если бы я мог заставить nose запустить этот код до того, как его плагин doctest даже попытается импортировать этот модуль.

Похоже, мне лучше всего написать правильный тестовый скрипт верхнего уровня, который обрабатывает набор тестов...


person burnpanck    schedule 18.10.2014    source источник
comment
Модуль нельзя импортировать без X или Y плохо пахнет, это означает, например, что ни pydoc, ни pylint нельзя использовать в вашем коде. Изменение модуля во время импорта, потому что он тестируется, как бы сводит на нет весь смысл тестирования, не так ли?   -  person Dima Tisnek    schedule 16.04.2015
comment
У меня по сути два случая: отсутствуют необязательные внешние зависимости и модули, использующие новый синтаксис python 3, протестированные под python 2. В обоих случаях тестирование по сути напрасно, модули недоступны. Я хочу подавить сбои теста и, возможно, заменить их сообщениями о том, что какая-то часть библиотеки недоступна из-за отсутствующих зависимостей.   -  person burnpanck    schedule 17.04.2015
comment
nosetests -v --with-doctest pckg вы позволяете Python 2 видеть код Python 3, потому что запускаете --with-doctest из верхнего каталога. Запустите тесты как unittest, так и doctest из папки tests. Для успешного выполнения doctest извлеките его в обычный текстовый файл, как я сделал, чтобы вы могли условно импортировать.   -  person Nizam Mohamed    schedule 22.04.2015
comment
› Функция, подобная ~/pckg/tests/test_py3only.py:setup_module(), может решить эту проблему. setup_ функции предназначены для модульного тестирования.   -  person Nizam Mohamed    schedule 22.04.2015
comment
Я не хочу извлекать тесты документации в текстовый файл: они составляют часть строки документации. Я хочу проверить их, чтобы убедиться, что они верны. Функции setup_ составляют часть так называемых fixtures, и я надеялся, что есть способ указать их для встроенных тестов документации. Думаю, это было напрасно.   -  person burnpanck    schedule 22.04.2015
comment
nosetests имеет -I REGEX возможность игнорировать файлы.   -  person Nizam Mohamed    schedule 22.04.2015


Ответы (2)


Конкретные тестовые файлы, каталоги, классы или методы можно исключить с помощью nose-exclude плагина для носа. Он имеет --exclude-* вариантов.

Чтобы справиться с отсутствующими модулями, вы должны исправить sys.modules с помощью mock.

Например, в модуле mycalc есть класс Calc, но у меня нет к нему доступа, потому что он отсутствует. И есть еще два модуля, mysuper_calc и mysuper_calc3, последний специфичен для Python 3. Эти два модуля импорта mycalc и mysuper_calc3 не должны тестироваться в Python 2. Как протестировать их из модуля, который находится в текстовом файле? Я предполагаю, что это ситуация ОП.

кальк/mysuper_calc3.py

from sys import version_info
if version_info[0] != 3:
    raise Exception('Python 3 required')
from mycalc import Calc
class SuperCalc(Calc):
    '''This class implements an enhanced calculator
    '''
    def __init__(self):
        Calc.__init__(self)

    def add(self, n, m):
        return Calc.add(self, n, m)

calc/mysuper_calc.py

from mycalc import Calc

class SuperCalc(Calc):
    '''This class implements an enhanced calculator
    '''
    def __init__(self):
        Calc.__init__(self)

    def add(self, n, m):
        return Calc.add(self, n, m)

Теперь, чтобы издеваться над mycalc,

>>> from mock import Mock, patch
>>> mock = Mock(name='mycalc')

Модуль mycalc имеет класс Calc с методом add. Я тестирую метод экземпляра SuperCalc add с 2+3.

>>> mock.Calc.add.return_value = 5  

Теперь патчи sys.modules и mysuper_calc3 можно условно импортировать в блок with.

>>> with patch.dict('sys.modules',{'mycalc': mock}):
...     from mysuper_calc import SuperCalc
...     if version_info[0] == 3:
...         from mysuper_calc3 import SuperCalc

расчет/doctest/mysuper_calc_doctest.txt

>>> from sys import version_info
>>> from mock import Mock, patch
>>> mock = Mock(name='mycalc')
>>> mock.Calc.add.return_value = 5

>>> with patch.dict('sys.modules',{'mycalc': mock}):
...     from mysuper_calc import SuperCalc
...     if version_info[0] == 3:
...         from mysuper_calc3 import SuperCalc
>>> c = SuperCalc()
>>> c.add(2,3)
5

Файл mysuper_calc_doctest.txt должен находиться один в своем собственном каталоге, иначе nosetests будет искать doctest в нетестовых модулях.

PYTHONPATH=.. nosetests --with-doctest --doctest-extension=txt --verbosity=3

Доктест: mysuper_calc_doctest.txt ... ок


Выполнить 1 тест за 0,038 с

OK

Обертка вокруг nosetests для обнаружения Python 3, который передает файлы .py без синтаксических ошибок в nosetests

mynosetests.py

import sys
from subprocess import Popen, PIPE
from glob import glob

f_list = []

py_files = glob('*py')
try:
    py_files.remove(sys.argv[0])
except ValueError:
    pass

for py_file in py_files:
    try:
        exec open(py_file)
    except SyntaxError:
        continue
    else:
        f_list.append(py_file)

proc = Popen(['nosetests'] + sys.argv[1:] + f_list,stdout=PIPE, stderr=PIPE)
print('%s\n%s' % proc.communicate())
sys.exit(proc.returncode)
person Nizam Mohamed    schedule 19.04.2015
comment
Конечно, исправление sys.modules фиктивными модулями было бы одним из возможных действий, которые мой прибор мог бы выполнить, чтобы сделать их импортируемыми. Проблема в том, где разместить этот код фикстуры, чтобы nose запускал его до того, как nose-doctest попытается импортировать модуль? - person burnpanck; 21.04.2015
comment
Кроме того, хотя nose-exclude позволит мне исключить файлы с синтаксисом python 3 из тестов, это можно сделать только статически из файла node.cfg или из командной строки. Я бы предпочел, чтобы прибор принял решение о пропуске, так как я хочу пропустить только при тестировании Python 2, но не при тестировании Python 3, и без какого-либо ручного взаимодействия. - person burnpanck; 21.04.2015
comment
«приспособление» в вашем контексте неясно. Покажите нам какой-нибудь пример кода, описывающий вашу ситуацию: nose не может импортировать, а 'fixutre' принимает решение о пропуске. - person Nizam Mohamed; 21.04.2015
comment
Спасибо за очень подробную правку. В моем случае тесты документации встроены в строки документации внутри модулей. Я полагаюсь на nose-doctest, чтобы собрать их. Таким образом, когда они запускаются, уже слишком поздно делать фиктивное исправление, так как модуль уже импортирован. Однако в вашем случае doctest, похоже, не проверяет ваши модули на наличие строк документации, иначе вы получите исключение при импорте mysuper_calc3. Я опубликую минимальный рабочий пример (должен быть с самого начала, конечно). - person burnpanck; 21.04.2015
comment
Вы можете исправить прямо в модуле, в котором отсутствует импорт. - person Nizam Mohamed; 21.04.2015
comment
Что действительно решает мою первую проблему, но не SyntaxError. (На самом деле, я просто пропускаю, вызывая исключение ModuleDepsNotMet, которое происходит от ImportError и unittest.SkipTest. - person burnpanck; 21.04.2015

Нос имеет следующий вариант:

  --doctest-fixtures=SUFFIX
                    Find fixtures for a doctest file in module with this
                    name appended to the base name of the doctest file

Возможно, можно выделить фикстуры в отдельный файл?

person Dima Tisnek    schedule 16.04.2015
comment
Звучит интересно, важный вопрос заключается в том, будет ли фикстура запускаться до того, как модуль будет импортирован. Я попробую. - person burnpanck; 17.04.2015
comment
К сожалению, --doctest-fixtures рассматривается только для немодульных доктестов. Следовательно, это не может помочь мне скрыть SyntaxError во время импорта, когда doctest ищет в модулях тесты. В любом случае спасибо за ответ! - person burnpanck; 19.04.2015