导入一个模块为什么会破坏我的doctest(Python 2.7)?

3

我尝试在我的Python 2.7程序中,在类的doctest中使用StringIO实例。但是测试没有任何输出,只得到了"Got nothing"的响应。

以下简化的测试用例演示了这个错误:

#!/usr/bin/env python2.7
# encoding: utf-8

class Dummy(object):
    '''Dummy: demonstrates a doctest problem

    >>> from StringIO import StringIO
    ... s = StringIO()
    ... print("s is created")
    s is created
    '''

if __name__ == "__main__":
    import doctest
    doctest.testmod()

期望行为:测试通过。

观察到的行为:测试失败,输出结果如下:

% ./src/doctest_fail.py
**********************************************************************
File "./src/doctest_fail.py", line 7, in __main__.Dummy
Failed example:
    from StringIO import StringIO
    s = StringIO()
    print("s is created")
Expected:
    s is created
Got nothing
**********************************************************************
1 items had failures:
   1 of   1 in __main__.Dummy
***Test Failed*** 1 failures.

为什么这个doctest失败了?我需要做出什么改变才能在我的doctest中使用类似StringIO的功能(一个具有文件接口的字面字符串)?


4
为什么在一些非连续行中使用 ... 而不是 >>> - user2357112
正如答案所表明的那样,问题实际上是关于doctest语法,而不是StringIO。我正在删除StringIO标签,并重新措辞问题,不再提到StringIO。 - Jim DeLaHunt
2个回答

6

造成doctest解析器困惑的是续行语法(...)。以下写法可行:

#!/usr/bin/env python2.7
# encoding: utf-8

class Dummy(object):
    '''Dummy: demonstrates a doctest problem

    >>> from StringIO import StringIO
    >>> s = StringIO()
    >>> print("s is created")
    s is created
    '''

if __name__ == "__main__":
    import doctest
    doctest.testmod()

谢谢!你说得对,我的连续行语法使用有问题,改用 >>> 语法可以解决。但你没有进一步解释为什么我的语法在 doctest 语义上是错误的。我会自己写一个答案来解释这个问题。 - Jim DeLaHunt

1
[在wim正确的回答基础上,进一步解释为什么会失败,并查看底层doctest语义。]

这个例子失败了,因为它使用了PS2语法(...)而不是PS1语法(>>>)在单独的简单语句前面。

...更改为>>>

#!/usr/bin/env python2.7
# encoding: utf-8

class Dummy(object):
    '''Dummy: demonstrates a doctest problem

    >>> from StringIO import StringIO
    >>> s = StringIO()
    >>> print("s is created")
    s is created
    '''

if __name__ == "__main__":
    import doctest
    doctest.testmod()

现在,已经更正的示例被重命名为doctest_pass.py,可以无误地运行。它不会产生任何输出,这意味着所有的测试都通过了。
% src/doctest_pass.py                       

为什么>>>语法是正确的?Python文档测试库参考中的25.2.3.2. 如何识别文档字符串示例?应该是找到答案的地方,但它对这个语法并不十分清楚。Doctest通过扫描文档字符串来查找“示例”。在看到PS1字符串>>>时,它会将该字符串到行尾之间的所有内容作为示例。它还将任何以PS2字符串...开头的后续行附加到示例中(请参阅:doctest.DocTestParser类中的_EXAMPLE_RE,第584-595行)。它会将后续行,直到下一个空行或以PS1字符串开头的行,作为预期输出。

Doctest将每个示例编译为Python的“交互式语句”,使用内置函数compile()exec语句中进行编译(参见:doctest.DocTestRunner.__run(),第1314-1315行)。

交互式语句”是以换行符结尾的语句列表或复合语句。复合语句,例如iftry语句,“一般而言,[跨越]多行,尽管在简单版本中,整个复合语句可以包含在一行中。”以下是一个多行复合语句的示例:

if 1 > 0:
    print("As expected")
else:
    print("Should not happen")

一个语句列表是一条或多条简单语句在同一行上,用分号隔开。
from StringIO import StringIO
s = StringIO(); print("s is created")

因为一个示例中有三个简单语句且没有分号分隔符,所以问题的doctest失败了。将PS2字符串更改为PS1字符串会成功,因为它将docstring转换为三个示例序列,每个示例都有一个简单语句。尽管这三行代码一起工作来设置一个功能的测试,但它们不是单个测试夹具。它们是三个测试,其中两个设置状态但实际上并没有测试主要功能。
顺便说一下,您可以使用-v标志看到doctest识别的示例数。请注意,它说:“__main__.Dummy中的3个测试”。人们可能认为这三行代码是一个测试单元,但doctest看到三个示例。前两个示例没有预期输出。当示例执行并生成没有输出时,这被视为“通过”。
% src/doctest_pass.py -v
Trying:
    from StringIO import StringIO
Expecting nothing
ok
Trying:
    s = StringIO()
Expecting nothing
ok
Trying:
    print("s is created")
Expecting:
    s is created
ok
1 items had no tests:
    __main__
1 items passed all tests:
   3 tests in __main__.Dummy
3 tests in 2 items.
3 passed and 0 failed.
Test passed.

在单个文档字符串中,示例按顺序执行。每个示例的状态更改会保留给同一文档字符串中的后续示例。因此,import语句定义了模块名称,s =赋值语句使用该模块名称并定义变量名称,依此类推。当doctest文档25.2.3.3. What’s the Execution Context?间接披露时,它说,“示例可以自由地使用...在运行的文档字符串中先前定义的名称。”
该部分中前面的句子“每次doctest找到要测试的文档字符串时,它都会使用M全局变量的浅拷贝,以便... M中的一个测试不能留下碎屑,意外地允许另一个测试工作”有点误导性。确实,M中的一个测试无法影响不同文档字符串中的测试。但是,在单个文档字符串中,先前的测试肯定会留下痕迹,这可能会影响后来的测试。
为什么Python库参考手册中的doctest示例25.2.3.2. 如何识别文档字符串示例?会显示一个带有...语法的示例?该示例展示了一个包含多行的复合语句if。第二行及其后续行使用PS2字符串标记。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接