2

我有一个相当基本的 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)

我得到一个 ImportError on __console__,我想这是有道理的。这对 python 或 ipython 都没有影响。

所以,我希望能够通过所有这三种方法成功运行文档测试,尤其是 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.
4

2 回答 2

8

根本问题是(通过它自己的模块)ipython玩奇怪的把戏,因此,当通过它的内省“所谓的模块”时,它存在——所以 doctest 不会递归到它。__main__FakeModuledoctest__dict__Foo

这是一个解决方案:

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]: 

只是设置 global__test__不起作用,再次因为将其设置为您所想的全局__main__并实际上并没有将它放在由__dict__恢复的实际对象中m = sys.modules['__main__'],而后者正是doctest内部使用的表达式(实际上它使用sys.modules.get,但这里不需要额外的预防措施,因为我们知道它__main__存在于sys.modules......它只是不是你期望的对象!-)。

此外,m.__test__ = globals()由于不同的原因,直接设置也不起作用:doctest检查其中的值__test__是否是字符串、函数、类或模块,并且如果没有一些选择,您不能保证globals()会满足该条件(实际上不会) . 在这里,我只选择类,如果您还想要函数或诸如此类的东西,则可以在调用中的 genexpor的子句中使用 an 。ifdict

python manage.py shell但_一个类似的方法应该有帮助(无论你的 Django shell 是使用 ipython,可用时的默认值,还是纯 Python):__test__在你获得的对象中适当地设置为sys.modules['__main__'](或者__console__,如果这是你传递给 doctest.testmod 的内容,我猜测)应该可以工作,因为它模仿了 doctest 将在内部执行的操作以定位您的测试字符串。

最后,对设计、建筑、简单、透明和“黑魔法”的哲学反思……:

所有这些努力基本上都是为了打败 ipython(也许是 Django,尽管它可能只是将那部分委托给 ipython)为您的“方便”而代表您所做的“黑魔法”......任何时候两个框架(或更多;-)各自独立地做着自己品牌的黑魔法,互操作性可能突然需要大量的努力并变得很方便;-)。

我并不是说在没有黑魔法、内省、假模块等的情况下(由 ipython、django 和/或 doctest 中的任何一个或多个)可以提供同样的便利;这些框架中的每一个的设计者和维护者都是出色的工程师,我希望他们已经彻底完成了他们的功课,并且只执行了最少量的黑魔法,这对于提供他们认为需要的用户便利是必不可少的。然而,即使在这种情况下,只要你想做一些超出框架作者设想的事情,“黑魔法”就会突然从方便的梦想变成调试的噩梦。

好吧,在这种情况下,也许不是一场噩梦,但我确实注意到这个问题已经开放了一段时间,即使有赏金的诱惑,它也没有得到很多答案——尽管你现在确实有两个答案可供选择来自,我使用__test__doctest 的特殊功能,@codeape 使用 Ironpython 的特殊__IP.magic_run功能。我更喜欢我的,因为它不依赖于任何内部或未记录的内容——它__test__是 doctest 的一个记录特征,同时__IP,随着这两个隐约可见的前导下划线,向我尖叫“深层内部结构,不要碰”;-)... 如果它在下一个版本中中断,我一点也不感到惊讶。尽管如此,品味问题——这个答案可以说被认为更“方便”。

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

于 2009-08-29T17:05:02.443 回答
2

以下作品:

$ 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)

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

编辑2:

以下脚本ipython_doctest使 ipython 以您想要的方式运行:

#! /usr/bin/env bash

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

该脚本创建了一个将%run argname在 IPython 中执行的 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]:
于 2009-08-26T20:10:54.760 回答