如何判断代码是否在doctest中运行?
我该如何让我的(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 个回答
顺便说一句(抱歉我来得有点晚,而且可能说的有点多),很多开发者认为“if test”是一种反模式。
也就是说,如果你的代码在测试时和实际运行时表现得不一样,那你就可能会遇到麻烦。即使你觉得这样做是有道理的。因此,上面那些评论赞扬你不使用这种方式的解决方案。当我想要使用“if test”这种模式时,我会尽量重构代码,让它不再需要这样做。
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 通常不是个好主意。我只在一些比较特殊的情况下这么做过——当我需要通过代码生成器创建测试用例,因为手动编写的数量太多,效率太低。不过如果你真的需要这么做,上面的测试还算不错。
我在看了 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