我正在编写test_examples.py
来测试python示例文件夹的执行情况。目前我使用glob
来解析文件夹,然后使用subprocess
来执行每个python文件。问题是这些文件中的一些是绘图,它们打开一个Figure
窗口,在窗口关闭之前停止。
关于这个问题的很多问题都提供了文件中的解决方案,但是如何在不进行任何修改的情况下在外部运行文件时抑制输出?
到目前为止,我所做的是:
import subprocess as sb
import glob
from nose import with_setup
def test_execute():
files = glob.glob("../*.py")
files.sort()
for fl in files:
try:
sb.call(["ipython", "--matplotlib=Qt4", fl])
except:
assert False, "File: %s ran with some errors\n" % (fl)
这种工作,因为它抑制了数字,但它不会抛出任何异常(即使程序有错误)。我也不是100%肯定它在做什么。它是否将所有数字附加到Qt4,或者在脚本完成后将图从内存中删除?
理想情况下,我希望理想情况下运行每个.py
文件并捕获其stdout
和stderr
,然后使用退出条件报告stderr
并使测试失败。然后当我运行nosetests
时,它将运行程序的examples文件夹并检查它们是否全部运行。
答案 0 :(得分:2)
您可以通过在每个源文件的顶部插入以下行来强制matplotlib使用Agg
后端(不会打开任何窗口):
import matplotlib
matplotlib.use('Agg')
这里是一个单行shell命令,它将动态地在my_script.py
的顶部插入这些行(不修改磁盘上的文件),然后将输出汇总到Python解释执行:
~$ sed "1i import matplotlib\nmatplotlib.use('Agg')\n" my_script.py | python
您应该能够使用subprocess
进行等效呼叫,如下所示:
p1 = sb.Popen(["sed", "1i import matplotlib\nmatplotlib.use('Agg')\n", fl],
stdout=sb.PIPE)
exit_cond = sb.call(["python"], stdin=p1.stdout)
您可以通过将stderr
和stdout
参数传递给stdout=
来捕获脚本中的stderr=
和sb.call()
。当然,这只适用于具有sed
实用程序的Unix环境。
这实际上是一个非常有趣的问题。我考虑了一下,我认为这是一个更优雅的解决方案(虽然仍然有点黑客):
#!/usr/bin/python
import sys
import os
import glob
from contextlib import contextmanager
import traceback
set_backend = "import matplotlib\nmatplotlib.use('Agg')\n"
@contextmanager
def redirected_output(new_stdout=None, new_stderr=None):
save_stdout = sys.stdout
save_stderr = sys.stderr
if new_stdout is not None:
sys.stdout = new_stdout
if new_stderr is not None:
sys.stderr = new_stderr
try:
yield None
finally:
sys.stdout = save_stdout
sys.stderr = save_stderr
def run_exectests(test_dir, log_path='exectests.log'):
test_files = glob.glob(os.path.join(test_dir, '*.py'))
test_files.sort()
passed = []
failed = []
with open(log_path, 'w') as f:
with redirected_output(new_stdout=f, new_stderr=f):
for fname in test_files:
print(">> Executing '%s'" % fname)
try:
code = compile(set_backend + open(fname, 'r').read(),
fname, 'exec')
exec(code, {'__name__':'__main__'}, {})
passed.append(fname)
except:
traceback.print_exc()
failed.append(fname)
pass
print ">> Passed %i/%i tests: " %(len(passed), len(test_files))
print "Passed: " + ', '.join(passed)
print "Failed: " + ', '.join(failed)
print "See %s for details" % log_path
return passed, failed
if __name__ == '__main__':
run_exectests(*sys.argv[1:])
从概念上讲,这与我以前的解决方案非常相似 - 它的工作原理是将测试脚本作为字符串读入,并在它们前面添加几行,这些行将导入matplotlib并将后端设置为非交互式。然后将该字符串编译为Python字节码,然后执行。主要优点是它应该与平台无关,因为不需要sed
。
如果像我一样,你倾向于像这样编写脚本,那么使用全局变量的{'__name__':'__main__'}
技巧是必要的:
def run_me():
...
if __name__ == '__main__':
run_me()
要考虑几点:
set_backend
技巧将无法正常工作,您仍然可以使用得到数字突然出现。最简单的方法是直接从shell(~$ python exectests.py testdir/ logfile.log
)运行它,或者从(i)python会话运行它,你没有为matplotlib设置交互式后端。如果您在ipython会话中的不同子流程中运行它,它也应该有用。contextmanager
技巧将stdin
和stdout
重定向到日志文件。请注意,这不是线程安全的,但我认为脚本打开子进程非常不寻常。答案 1 :(得分:0)
到现在为止,但是我想自己弄清楚类似的事情,这就是我到目前为止提出的。基本上,如果您的绘图正在调用matplotlib.pyplot.show
以显示该绘图,则可以使用mock来patch decorator来显示该方法。像这样:
from unittest.mock import patch
@patch('matplotlib.pyplot.show') # passes a mock object to the decorated function
def test_execute(mock_show):
assert mock_show() == None # shouldn't do anything
files = glob.glob("../*.py")
files.sort()
for fl in files:
try:
sb.call(["ipython", fl])
except:
assert False, "File: %s ran with some errors\n" % (fl)
基本上,补丁装饰器应将装饰函数中对matplotlib.pyplot.show
的所有调用替换为不执行任何操作的模拟对象。至少这就是理论上的工作方式。在我的应用程序中,我的终端仍在尝试打开图,这会导致错误。我希望它对您更好,如果我发现上述问题导致我的问题,请及时更新。
编辑:为了完整起见,您可能会通过调用matplotlib.pyplot.figure()
或matplotlib.pyplot.subplots()
来生成图形,在这种情况下,您将模拟出这些图形而不是{{ 1}}。与上面相同的语法,您只需要使用:
matplotlib.pyplot.show()
或:
@patch('matplotlib.pyplot.figure')