如何判断代码是否在doctest中运行?

7 投票
4 回答
1086 浏览
提问于 2025-04-17 06:15

我该如何让我的(Python 2.7)代码知道它是否在运行一个文档测试(doctest)呢?

情况是这样的:我有一个函数,它会把一些输出内容打印到一个作为参数传入的文件描述符,类似这样:

from __future__ import print_function

def printing_func(inarg, file=sys.stdout):
    # (do some stuff...)
    print(result, file=file)

但是当我在文档测试中使用 printing_func() 时,测试会失败;这是因为我在调用 print() 时指定了关键字参数 file,所以输出实际上是发送到了 sys.stdout,而不是文档测试模块设置的默认输出重定向,这样文档测试就看不到输出了。

那么,我该如何让 printing_func() 知道它是否在文档测试中运行,这样它就可以在调用 print() 时不传递 file 这个关键字参数呢?

4 个回答

0

顺便说一句(抱歉我来得有点晚,而且可能说的有点多),很多开发者认为“if test”是一种反模式。

也就是说,如果你的代码在测试时和实际运行时表现得不一样,那你就可能会遇到麻烦。即使你觉得这样做是有道理的。因此,上面那些评论赞扬你不使用这种方式的解决方案。当我想要使用“if test”这种模式时,我会尽量重构代码,让它不再需要这样做。

5

Niten 提出的 inside_doctest 的版本似乎太宽泛了。重新定义 sys.stdout 其实并不罕见,可能是为了记录日志,或者在其他测试框架中测试,而不是仅仅在 doctest 中使用,这样可能会导致错误的结果。

一个更精确的测试看起来是这样的:

import sys

def in_doctest():
    """
Determined by observation
    """
    if '_pytest.doctest' in sys.modules:
        return True
    ##
    if hasattr(sys.modules['__main__'], '_SpoofOut'):
        return True
    ##
    if sys.modules['__main__'].__dict__.get('__file__', '').endswith('/pytest'):
        return True
    ##
    return False


def test():
    """
    >>> print 'inside comments, running in doctest?', in_doctest()
    inside comments, running in doctest? True
    """
    print 'outside comments, running in doctest?', in_doctest()

if __name__ == '__main__':
    test()

in_doctest 用来测试 _SpoofOut 类,这是 doctest 用来替换 sys.stdout 的。doctest 模块还有其他属性也可以用同样的方法来验证。虽然你不能阻止其他模块使用相同的名字,但这个名字并不常见,所以这个测试应该还不错。

把上面的内容放在 test.py 文件里。在非 doctest 模式下运行 python test.py,结果是:

outside comments, running in doctest? False

在 doctest 详细模式下运行 python -m doctest test.py -v,结果是:

Trying:
    print 'inside comments, running in doctest?', in_doctest()
Expecting:
    inside comments, running in doctest? True
ok

我同意其他人的看法,让代码知道 doctest 通常不是个好主意。我只在一些比较特殊的情况下这么做过——当我需要通过代码生成器创建测试用例,因为手动编写的数量太多,效率太低。不过如果你真的需要这么做,上面的测试还算不错。

1

我在看了 doctest.py 之后找到了答案,特地在这里分享一下,以便后人参考...

Doctest 通过给 sys.stdout 赋值一个新的文件描述符来重定向标准输出。问题在于,我的函数描述在 doctest 重新定义之前就已经关闭了原来的 sys.stdout 文件描述符。

相反,如果我这样做:

def printing_func(inarg, file=None):
    # (do some stuff...)

    if file is None:
        file = sys.stdout

    print(result, file=file)

那么 printing_func() 就会捕获 sys 模块,而不是 sys.stdout。当它运行时,如果是在测试中,它会从 sys 中获取 doctest 重新定义的 stdout 属性。

编辑:这也提供了一种简单的方法来检查我们是否在 doctest 中运行:

def inside_doctest(original_stdout=sys.stdout):
    return original_stdout != sys.stdout

撰写回答