Проведение док-тестов через iPython и псевдо-консоли - PullRequest
2 голосов
/ 26 августа 2009

У меня довольно простой файл документации:

class Foo():
    """
    >>> 3+2
    5
    """

if __name__ in ("__main__", "__console__"):
    import doctest
    doctest.testmod(verbose=True)

, который работает как положено при запуске напрямую через python.

Однако в iPython я получаю

1 items had no tests:
    __main__
0 tests in 1 items.
0 passed and 0 failed.
Test passed.

Поскольку это часть проекта Django, и для него потребуется доступ ко всем соответствующим переменным, например, для настройки manage.py, я также могу запустить его с помощью модифицированной команды, которая использует code.InteractiveConsole, одним из результатов которого is __name__ получает значение '__console__'.

С кодом выше я получаю тот же результат, что и с iPython. Я попытался изменить последнюю строку на это:

 this = __import__(__name__)
 doctest.testmod(this, verbose=True)

и я получаю ImportError на __console__, что, я думаю, имеет смысл. Это не влияет ни на python, ни на ipython.

Итак, я хотел бы иметь возможность успешно проводить doctests через все эти три метода, особенно через InteractiveConsole, поскольку я ожидаю, что скоро понадобится магия пони Django.

Просто для пояснения, это то, что я ожидаю:

Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

Ответы [ 2 ]

8 голосов
/ 29 августа 2009

Основная проблема заключается в том, что ipython играет странные трюки с __main__ (через свой собственный FakeModule модуль), так что к тому времени doctest анализирует этот "предполагаемый модуль" через __dict__, Foo есть НЕ там - так что doctest не вернется в него.

Вот одно из решений:

class Foo():
    """
    >>> 3+2
    5
    """

if __name__ in ("__main__", "__console__"):
    import doctest, inspect, sys
    m = sys.modules['__main__']
    m.__test__ = dict((n,v) for (n,v) in globals().items()
                            if inspect.isclass(v))
    doctest.testmod(verbose=True)

Это производит в соответствии с просьбой:

$ ipython dot.py 
Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.__test__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Python 2.5.1 (r251:54863, Feb  6 2009, 19:02:12) 
  [[ snip snip ]]
In [1]: 

Простая установка глобального __test__ не работает, опять же, потому что установка его в глобальное значение, о котором вы думаете __main__, на самом деле НЕ помещает его в __dict__ реального объекта, который восстанавливается m = sys.modules['__main__'], и последнее является именно тем выражением, которое doctest использует для внутреннего использования (на самом деле оно использует sys.modules.get, но здесь нет необходимости в дополнительных мерах предосторожности, поскольку мы знаем, что __main__ существует в sys.modules ... это просто НЕ тот объект, который вы ожидаете! -).

Кроме того, просто установка m.__test__ = globals() напрямую не работает, по другой причине: doctest проверяет, что значения в __test__ являются строками, функциями, классами или модулями, и без некоторого выбора вы не можете гарантировать, что globals() удовлетворит это условие (на самом деле это не так). Здесь я выбираю только классы, если вам также нужны функции или еще что-то, вы можете использовать or в предложении if в genexp в вызове dict.

Я не знаю точно, как у вас работает оболочка Django, которая способна выполнить ваш скрипт (как я полагаю python manage.py shell не принимает аргументы, вы должны делать что-то еще, и я не могу точно догадаться что! -), но подобный подход должен помочь (независимо от того, использует ли ваша оболочка Django ipython, значение по умолчанию, если оно доступно, или обычный Python): соответственно установите __test__ в объекте, который вы получите как sys.modules['__main__'] (или __console__, если это то, что вы затем передаете в doctest.testmod, я думаю) должен работать, так как он имитирует то, что doctest будет делать внутри, чтобы найти ваши тестовые строки.

И, в заключение, философское осмысление дизайна, архитектуры, простоты, прозрачности и "черной магии" ...:

Все эти усилия - в основном то, что необходимо для победы над "черной магией", которую ipython (и, возможно, Django, хотя он может просто делегировать эту часть ipython) от вашего имени для вашего "удобства" ... любой В то время, когда две структуры (или больше ;-) независимо друг от друга делают каждый свой собственный бренд чёрной магии, взаимодействие может внезапно потребовать значительных усилий и стать чем угодно, НО удобным; -).

Я не говорю, что такое же удобство могло бы быть предоставлено (одним или несколькими из ipython, django и / или doctests) без чёрной магии, самоанализа, поддельных модулей и т. Д .; дизайнеры и сопровождающие каждого из этих фреймворков - превосходные инженеры, и я ожидаю, что они тщательно выполнили свою домашнюю работу и выполнили только минимальное количество черной магии, которое необходимо для обеспечения удобства пользователя, которое, по их мнению, им было необходимо. Тем не менее, даже в такой ситуации «черная магия» внезапно превращается из удобного сна в кошмар отладки, как только вы захотите сделать что-то хотя бы немного за пределами того, что задумал автор фреймворка.

Хорошо, может быть, в этом случае это не совсем кошмар, но я заметил, что этот вопрос был открыт некоторое время, и даже с приманкой щедрости он еще не получил много ответов - хотя у вас теперь есть два ответы, которые можно выбрать, мои используют специальную функцию __test__ doctest, @ codeape использует особую функцию __IP.magic_run ironpython. Я предпочитаю мой, потому что он не опирается на что-то внутреннее или недокументированное - __test__ - документированная особенность doctest, в то время как __IP, с этими двумя надвигающимися подчеркиваниями, кричит мне «глубокие внутренние органы, не трогайте»; -) ... если он сломается в следующем выпуске, я бы совсем не удивился. Тем не менее, дело вкуса - этот ответ, возможно, можно считать более «удобным».

Но это именно моя точка зрения: удобство может обойтись огромной ценой с точки зрения отказа от простоты, прозрачности и / или избежания внутренних / недокументированных / нестабильных функций; так что, как урок для всех нас, наименьшая чёрная магия, с которой мы можем справиться (даже ценой отказа от эпсилона удобства здесь и там), тем счастливее мы все будем в конечном итоге (и тем счастливее мы сделаем других разработчиков, которым необходимо использовать наши текущие усилия в будущем).

2 голосов
/ 27 августа 2009

работают следующие работы:

$ ipython
...
In [1]: %run file.py

Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

In [2]: 

Понятия не имею, почему ipython file.py не работает. Но вышеупомянутое - по крайней мере обходной путь.

EDIT:

Я нашел причину, почему она не работает. Это довольно просто:

  • Если вы не укажете модуль для тестирования в doctest.testmod(), предполагается, что вы хотите протестировать модуль __main__.
  • Когда IPython выполняет файл, переданный ему в командной строке, модуль __main__ является IPython __main__, а не вашим модулем. Итак, doctest пытается выполнить doctest в скрипте ввода IPython.

Следующее работает, но немного странно:

if __name__ == '__main__':
    import doctest
    import the_current_module
    doctest.testmod(the_current_module)

Так что в основном модуль импортирует сам себя (это «немного странная» часть). Но это работает. Что-то мне не нравится, ок. этот подход заключается в том, что каждый модуль должен включать свое собственное имя в источник.

РЕДАКТИРОВАТЬ 2:

Следующий скрипт, ipython_doctest, заставляет ipython вести себя так, как вы хотите:

#! /usr/bin/env bash

echo "__IP.magic_run(\"$1\")" > __ipython_run.py
ipython __ipython_run.py

Сценарий создает скрипт Python, который будет выполнять %run argname в IPython.

Пример:

$ ./ipython_doctest file.py
Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Python 2.5 (r25:51908, Mar  7 2008, 03:27:42) 
Type "copyright", "credits" or "license" for more information.

IPython 0.9.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object'. ?object also works, ?? prints more.

In [1]:
...