在doctest中如何进行代码行的延续/折行

12

我正在使用doctest.testmod()进行一些基本测试。我有一个返回长字符串的函数,比如说get_string()。类似于:

def get_string(a, b):
    r''' (a, b) -> c

    >>> get_string(1, 2)
    'This is \n\n a long \n string with new \
    space characters \n\n'
    # Doctest should work but does not.

    '''
    return ('This is \n\n a long \n string ' + \
            'with new space characters \n\n')

问题在于doctest测试未通过,因为它期望一个单行字符串,并将换行符视为\n字符。有没有办法解决这个问题?

PS:这不是我正在使用的实际函数,而是为了您的方便而精简的版本。


请注意,如果没有必要的话,您在代码中的return行中使用反斜杠是无用甚至有害的。括号足以继续行(而且应该优先考虑使用括号)。此外,即使是+也是多余的。 - Bakuriu
6个回答

13

您可以使用NORMALIZE_WHITESPACE选项(请参见选项全列表)。

这里是来自doctest文档的示例:

>>> print range(20) # doctest: +NORMALIZE_WHITESPACE
[0,   1,  2,  3,  4,  5,  6,  7,  8,  9,
10,  11, 12, 13, 14, 15, 16, 17, 18, 19]

8
我认为你没有理解doctest的工作原理。它仅检查输出是否完全相同(仅有非常小的可能变化,如使用省略号),而不是检查输出是否在某种程度上“等效”。来自文档的描述如下:

doctest模块搜索看起来像交互式Python会话的文本片段,然后执行这些会话以验证它们正好按照所示进行。

Doctest将输出(而不是字符串文字、Python表达式或其他内容。原始输出字节)与提供的示例输出内容匹配。由于它不知道引号之间的文本表示字符串文字,因此无法按照您的意愿解释它。
换句话说:您唯一能做的就是将整个输出放在一行中,如下所示:
>>> get_string(1, 2)
    'This is \n\n a long \n string with new space characters \n\n'

如果输出结果过长,您可以尝试修改示例以生成较短的字符串(例如,将其截断为50个字符:get_string(1, 2)[:50])。如果您查看不同项目的doctest,您会发现不同的技巧,以使doctest更易读,同时提供可靠的输出。

4

如果你正在对输出中的一个长的单行字符串进行测试,可以使用doctest的省略特性(ELLIPSIS)将匹配字符串保持在80个字符以内,以符合PEP8标准。其中...将匹配任何字符串。虽然它通常用于变量输出,比如对象地址,但它也可以很好地处理固定的(长)输出,例如:

def get_string(a, b):
    r''' (a, b) -> c

    >>> get_string(1, 2)  # doctest: +ELLIPSIS
    'This is ... string with newline characters \n\n'
    '''
    return ('This is \n\n a long \n string '
            'with newline characters \n\n')

匹配过程中可能存在一定程度的精度损失,但通常对测试结果影响不大。


1

来自doctest的文档:

如果您在交互会话中通过反斜杠续行,或因任何其他原因使用反斜杠,则应使用原始文档字符串,该字符串将完全保留您键入的反斜杠:

>>> def f(x):
...     r'''Backslashes in a raw docstring: m\n'''
>>> print f.__doc__
Backslashes in a raw docstring: m\n

否则,您可以使用双反斜杠。

1

一种简单的解决方案是>>> repr(get_string(1,2)),它会转义换行符并仅在测试中使用单行字符串。

我更喜欢一种可以说出来的解决方案:

>>> get_string(1,2)
first line
second line

fourth

在你的情况下,这是一个问题,因为你有尾随的空格。
另外要注意的是,无法测试行继续字符。
"a" + \
"b"

完全等同于

"a" + "b"

"ab"

至目前为止,这听起来像是最好的解决方案,虽然我也想测试换行符,因为如果它们放置不当,在这种情况下我的程序可能会出现问题。不过还是谢谢! - JCOC611
repr()允许这样做,所以我不明白你的评论。 - Aaron Digulla
实际上,我还没有能够使用repr(...)实现doctest。它确实转义了换行符,但是行继续仍然被解释为换行符,因此测试未通过。你能给我展示一下它的样子吗? - JCOC611
啊,没有办法测试一行代码的延续字符,因为反斜杠后面跟着一个换行符会被简单地吞掉。在结果中根本不会出现它。解析器在 string 类有机会看到它之前就移除了它。因此,在调用该方法后,"a" + \ "b""a" + "b""ab" 完全相同。除非您向 Python 解释器请求方法的源代码并查找相应的行,否则无法编写此类测试。 - Aaron Digulla
哦,我遇到麻烦的行延续是在文档字符串中的那一行。实际上,我正在对单行字符串进行测试(实际函数中的行延续并不重要)。然而,问题在于doctest中的字符串被解释为有多行。 - JCOC611
你不能在文档字符串中使用行连续符号。我非常确定 Python 方法的 输出 不是由 Python 解析的,因此没有人可以删除 LC。你需要将一个非常长的单行字符串粘贴到 doctest 中。 - Aaron Digulla

0
另一个选择是使用pprint来美化输出,而不是手动处理,这可能更受您的文档读者欢迎。
POEM = """ My mother groaned, my father wept:
Into the dangerous world I leapt,
Helpless, naked, piping loud,
Like a fiend hid in a cloud.

Struggling in my father’s hands,
Striving against my swaddling bands,
Bound and weary, I thought best
To sulk upon my mother’s breast."""

def poem():
    """
    Enumerate the lines of INFANT SORROW

    >>> import pprint
    >>> pprint.pprint(poem())
    {0: ' My mother groaned, my father wept:',
     1: 'Into the dangerous world I leapt,',
     2: 'Helpless, naked, piping loud,',
     3: 'Like a fiend hid in a cloud.',
     4: '',
     5: 'Struggling in my father’s hands,',
     6: 'Striving against my swaddling bands,',
     7: 'Bound and weary, I thought best',
     8: 'To sulk upon my mother’s breast.'}
    """
    lines = POEM.split("\n")
    return {lineno:line for lineno, line in enumerate(lines)}


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