如何在unittest中捕获python子进程stdout



我正在尝试编写一个单元测试,该测试执行一个向stdout写入、捕获输出并检查结果的函数。有问题的函数是一个黑盒:我们无法改变它如何编写输出。出于这个例子的目的,我对它进行了相当大的简化,但本质上该函数使用subprocess.call().生成输出

无论我尝试什么,我都无法捕获输出。它总是被写入屏幕,而测试失败是因为它什么都没捕获。我同时尝试了print()和os.system()。使用print()我可以捕获stdout,但也不能使用os.system.()。

它也不是特定于单元测试的。我写了一个没有这个的测试例子,结果是一样的。

类似的问题已经被问了很多,答案似乎都归结为使用子流程。Popen()和communicate(),但这需要更改黑盒。我确信有一个答案我还没有找到,但我被难住了。

我们使用的是Python-2.7。

无论如何,我的示例代码是:

#!/usr/bin/env python
from __future__ import print_function
import sys
sys.dont_write_bytecode = True
import os
import unittest
import subprocess
from contextlib import contextmanager
from cStringIO import StringIO
# from somwhere import my_function
def my_function(arg):
#print('my_function:', arg)
subprocess.call(['/bin/echo', 'my_function: ', arg], shell=False)
#os.system('echo my_function: ' + arg)
@contextmanager
def redirect_cm(new_stdout):
old_stdout =  sys.stdout
sys.stdout =  new_stdout
try:
yield
finally:
sys.stdout = old_stdout
class Test_something(unittest.TestCase):
def test(self):
fptr = StringIO()
with redirect_cm(fptr):
my_function("some_value")
self.assertEqual("my_function: some_valuen", fptr.getvalue())
if __name__ == '__main__':
unittest.main()

上述代码中存在两个问题

  1. StringIO fptr不由当前进程和派生进程共享,即使派生进程已将结果写入StringIO对象,我们也无法在当前进程中获得结果

  2. 更改sys.stdout不会影响操作系统模块中os.popen()os.system()exec*()系列功能执行的进程的标准I/O流

是一个简单的解决方案

  1. 使用os.pipe在两个进程之间共享结果

  2. 使用os.dup2而不是更改sys.stdout

显示了如下演示示例

import sys
import os
import subprocess
from contextlib import contextmanager

@contextmanager
def redirect_stdout(new_out):
old_stdout = os.dup(1)
try:
os.dup2(new_out, sys.stdout.fileno())
yield
finally:
os.dup2(old_stdout, 1)

def test():
reader, writer = os.pipe()
with redirect_stdout(writer):
subprocess.call(['/bin/echo', 'something happened what'], shell=False)
print os.read(reader, 1024)

test()

最新更新