通过iPython和伪控制台运行doctests

时间:2009-08-26 19:33:34

标签: python django ipython

我有一个相当基本的doctestable文件:

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,其中一个结果__name__设置为“__console__”。

使用上面的代码,我得到的结果与iPython相同。我尝试将最后一行更改为:

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

我在__console__上得到一个ImportError,这是有意义的,我想。这对python或ipython都没有影响。

所以,我希望能够通过所有这三种方法成功运行doctests,尤其是InteractiveConsole方法,因为我希望很快就会需要Django pony magic。

为了澄清,这正是我所期待的:

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 个答案:

答案 0 :(得分:8)

根本问题是ipython使用__main__(通过其自己的FakeModule模块)播放奇怪的技巧,以便在doctest时反省“涉嫌模块” “通过其__dict__Foo NOT - 所以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__的{​​{1}}中由m = sys.modules['__main__']恢复的实际对象,后者恰好是内部使用的doctest表达式(实际上它使用sys.modules.get,但由于我们知道这一点,因此不需要额外的预防措施__main__中存在sys.modules ...它不是您期望的对象! - 。)。

另外,直接设置m.__test__ = globals()也不起作用,原因不同:doctest检查__test__中的值是字符串,函数,类还是模块,没有有些选择你无法保证globals()能满足这个条件(事实上它不会)。在这里,我只选择课程,如果您还想要函数或其他什么,可以在or调用中的genexp的if子句中使用dict

我不确切知道你是如何运行能够执行你的脚本的Django shell(因为我相信python manage.py shell不接受参数,你必须做其他事情,我不能猜测究竟是什么! - ),但类似的方法应该有所帮助(无论你的Django shell是使用ipython,默认是可用的,还是普通的Python):在你获得的对象__test__中适当地设置sys.modules['__main__'] (或者__console__,如果那就是你传递给doctest.testmod的东西,我想)应该有效,因为它模仿doctest将在内部做什么来找到你的测试字符串。

并且,总而言之,对设计,建筑,简洁,透明度和“黑魔法”的哲学反思......:

所有这些努力基本上都是为了击败ipython(也许是Django,虽然它可能只是将那部分委托给ipython)的“黑魔法”所需要的,为你的“方便”代表你做...两个框架(或更多;-)独立完成每个自己的黑魔法品牌的时间,互操作性可能突然需要大量的努力并成为任何方便的东西; - )。

我并不是说可以提供同样的便利(ipython,django和/或doctests中的任何一个或多个)没有黑魔法,内省,虚假模块等等;每个框架的设计师和维护者都是精湛的工程师,我希望他们能够彻底完成他们的作业,并且只执行最少量的黑魔法,这对于提供他们认为需要的用户便利性是不可或缺的。尽管如此,即使在这样的情况下,只要你想要在框架作者构思的范围之外做一些事情,“黑魔法”突然从一个方便的梦想转变为调试的噩梦。

好吧,也许在这种情况下不是一场噩梦,但我确实注意到这个问题已经开放了一段时间,即使有了赏金的诱惑,它还没有得到很多答案 - 尽管你现在有两个使用docpy的__test__特殊功能,@ codeape使用铁心的奇特__IP.magic_run功能来挑选我的答案。我更喜欢我的,因为它不依赖于内部或无证的任何东西 - __test__是doctest的记录特征,而__IP,这两个迫在眉睫的领先下划线,尖叫“深入的内部,不要触摸“对我来说; - )......如果它在下一个发布时断开,我就不会感到惊讶。品味问题 - 答案可能被认为更“方便”。

但是,这正是我的观点:在放弃简单性,透明度和/或避免内部/无证/不稳定特征方面,便利可能会付出巨大代价;所以,作为我们所有人的一课,我们可以逃脱最少的黑魔法(即使以放弃方便ε的方式付出代价),从长远来看,我们都会更幸福(并且我们会让其他需要利用我们当前努力的开发人员更快乐。)

答案 1 :(得分:1)

以下作品:

$ 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不起作用。但上述至少是一种解决方法。

修改

我找到了它无效的原因。这很简单:

  • 如果您未在doctest.testmod()中指定要测试的模块,则会假定您要测试__main__模块。
  • 当IPython执行在命令行上传递给它的文件时,__main__模块是IPython的__main__,而不是你的模块。因此,doctest尝试在IPython的入门脚本中执行doctests。

以下作品,但感觉有点奇怪:

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

所以基本上模块导入自己(这是“感觉有点奇怪”部分)。但它的确有效。我不喜欢abt的东西。这种方法是每个模块都需要在源代码中包含自己的名称。

编辑2:

以下脚本ipython_doctest使ipython的行为符合您的要求:

#! /usr/bin/env bash

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

该脚本创建一个将在IPython中执行%run argname的python脚本。

示例:

$ ./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]: