14

我想使用doctests来测试某些警告的存在。例如,假设我有以下模块:

from warnings import warn

class Foo(object):
    """
    Instantiating Foo always gives a warning:

    >>> foo = Foo()
    testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
    >>> 
    """

    def __init__(self):
        warn("Boo!", UserWarning)

如果我python -m doctest testdocs.py在课堂上运行 doctest 并确保打印了警告,我会得到:

testdocs.py:14: UserWarning: Boo!
  warn("Boo!", UserWarning)
**********************************************************************
File "testdocs.py", line 7, in testdocs.Foo
Failed example:
    foo = Foo()
Expected:
    testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
Got nothing
**********************************************************************
1 items had failures:
   1 of   1 in testdocs.Foo
***Test Failed*** 1 failures.

看起来警告正在打印,但没有被 doctest 捕获或注意到。我猜这是因为警告打印到sys.stderr而不是sys.stdout. 但即使我sys.stderr = sys.stdout在模块末尾说,也会发生这种情况。

那么有什么方法可以使用 doctests 来测试警告吗?我在文档或我的谷歌搜索中找不到任何一种方式或其他方式。

4

7 回答 7

4

这不是最优雅的方式,但它对我有用:

from warnings import warn

class Foo(object):
    """
    Instantiating Foo always gives a warning:

    >>> import sys; sys.stderr = sys.stdout
    >>> foo = Foo() # doctest:+ELLIPSIS
    /.../testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
    """

    def __init__(self):
        warn("Boo!", UserWarning)

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

不过,这可能不适用于 Windows,因为 UserWarning 输出中报告的路径必须以斜杠开头,就像我编写此测试的方式一样。你也许能想出一些更好的 ELLIPSIS 指令的咒语,但我不能。

于 2010-03-19T16:52:01.793 回答
4

Python 文档的测试警告部分专门针对此主题。但是,总而言之,您有两种选择:

(A) 使用catch_warnings上下文管理器

这是官方文档中推荐的课程。但是,catch_warnings上下文管理器仅在 Python 2.6 中出现。

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

(B) 将警告升级为错误

如果之前没有看到警告——因此已在警告注册表中注册——那么您可以设置警告以引发异常并捕获它。

import warnings


def fxn():
    warnings.warn("deprecated", DeprecationWarning)


if __name__ == '__main__':
    warnings.simplefilter("error", DeprecationWarning)

    try:
        fxn()
    except DeprecationWarning:
        print "Pass"
    else:
        print "Fail"
    finally:
        warnings.simplefilter("default", DeprecationWarning)
于 2010-03-20T20:39:50.027 回答
2

您面临的问题是warnings.warn()调用warnings.showwarning(),它将结果写入warnings.formatwarning()文件,默认为sys.stderr.

(见:http ://docs.python.org/library/warnings.html#warnings.showwarning )

如果您使用的是 Python 2.6,则可以使用warnings.catch_warnings()上下文管理器轻松修改警告的处理方式,包括临时替换warnings.showwarning()to write to的实现sys.stdout。那将是处理此类事情的正确方法。

(见:http ://docs.python.org/library/warnings.html#available-context-managers )

如果您想要快速而肮脏的 hack,请将重定向sys.stderr到的装饰器放在一起sys.stdout

def stderr_to_stdout(func):
    def wrapper(*args):
        stderr_bak = sys.stderr
        sys.stderr = sys.stdout
        try:
            return func(*args)
        finally:
            sys.stderr = stderr_bak
    return wrapper

然后你可以在你的 doctest 中调用一个修饰函数:

from warnings import warn
from utils import stderr_to_stdout

class Foo(object):
    """
    Instantiating Foo always gives a warning:

    >>> @stderr_to_stdout
    ... def make_me_a_foo():
    ...     Foo()
    ...
    >>> make_me_a_foo()
    testdocs.py:18: UserWarning: 
      warn("Boo!", UserWarning)
    >>>
    """ 
    def __init__(self):
        warn("Boo!", UserWarning)

通过:

$ python -m doctest testdocs.py -v
Trying:
    @stderr_to_stdout
    def make_me_a_foo():
        Foo()
Expecting nothing
ok
Trying:
    make_me_a_foo()
Expecting:
    testdocs.py:18: UserWarning: Boo!
      warn("Boo!", UserWarning)
ok
[...]
2 passed and 0 failed.
于 2010-03-19T16:44:45.753 回答
0

文档建议您可以在运行 doctest 时通过-Wd以始终触发警告。

于 2010-03-10T16:39:49.293 回答
0

也许您可以尝试嘲笑(补丁打印!)麻烦的一点。我承认这会给文档字符串增加一些混乱,但它可能值得一试。

如果您希望在保留当前语法的同时使用该方法,也许您可​​以尝试为 doctest 实现一个自定义包装器,该包装器生成缺少的代码,然后执行固定测试。如果可能的话,这可能是最好避免的。

或者,您可以创建一个完全自定义的 doctest 运行器,但我想您更愿意避免这种情况。:)

于 2010-03-19T08:15:56.983 回答
0

将过滤器设置为“始终”是否可以满足需要?

> cat warnme.py 
import warnings

for i in range(3):
    warnings.warn("oh noes!")

> python warnme.py 
warnme.py:4: UserWarning: oh noes!
warnings.warn("oh noes!")

> cat warnme2.py 
import warnings

warnings.simplefilter("always")
for i in range(3):
    warnings.warn("oh noes!")

> python warnme2.py 
warnme2.py:5: UserWarning: oh noes!
warnings.warn("oh noes!")
warnme2.py:5: UserWarning: oh noes!
warnings.warn("oh noes!")
warnme2.py:5: UserWarning: oh noes!
warnings.warn("oh noes!")
于 2021-05-18T14:04:06.617 回答
-1

这是为什么文档测试不适用于所有测试的一个例子。如果您的文档字符串中有内联示例并且需要对其进行测试,那是一回事,但正如您发现的那样,您想要测试的某些行为最好通过字符串匹配来完成。以及不需要将文档字符串与所有测试机制混杂在一起的情况。

于 2010-03-21T19:16:30.230 回答