简体   繁体   English

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

[英]How to capture python subprocess stdout in unittest

I am trying to write a unit test that executes a function that writes to stdout, capture that output, and check the result. 我正在尝试编写一个单元测试,该单元测试执行写入stdout的功能,捕获该输出并检查结果。 The function in question is a black box: we can't change how it is writing it's output. 有问题的函数是一个黑匣子:我们无法更改其写入输出的方式。 For purposes of this example I've simplified it quite a bit, but essentially the function generates its output using subprocess.call(). 出于本示例的目的,我已经对其进行了相当多的简化,但是实质上该函数使用subprocess.call()生成其输出。

No matter what I try I can't capture the output. 无论我尝试什么,都无法捕获输出。 It is always written to the screen, and the test fails because it captures nothing. 它始终被写入屏幕,并且测试失败,因为它没有捕获任何内容。 I experimented with both print() and os.system(). 我尝试了print()和os.system()。 With print() I can capture stdout, but not with os.system() either. 使用print()可以捕获标准输出,但是不能使用os.system()捕获。

It's also not specific to unittesting. 它也不特定于单元测试。 I've written my test example without that with the same results. 我没有用相同的结果编写测试示例。

Questions similar to this have been asked a lot, and the answers all seem to boil down to use subprocess.Popen() and communicate(), but that would require changing the black box. 与此类似的问题已经问了很多,答案似乎都归结为使用subprocess.Popen()和communication(),但这将需要更改黑框。 I'm sure there's an answer I just haven't come across, but I'm stumped. 我敢肯定我还没有找到答案,但是我很困惑。

We are using Python-2.7. 我们正在使用Python-2.7。

Anyway my example code is this: 无论如何,我的示例代码是这样的:

#!/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_value\n", fptr.getvalue())

if __name__ == '__main__':
    unittest.main()

There are two issues in the above code 上面的代码有两个问题

  1. StringIO fptr does not shared by the current and the spawned process, we could not get the result in current process even if the spawned process has written result to StringIO object StringIO fptr不被当前和生成的进程共享,即使生成的进程已将结果写入StringIO对象,我们也无法在当前进程中获取结果

  2. Changing sys.stdout doesn't affect the standard I/O streams of processes executed by os.popen() , os.system() or the exec*() family of functions in the os module 更改sys.stdout不会影响 os模块中os.popen()os.system()exec*()系列函数执行的标准I / O流程

A simple solution is 一个简单的解决方案是

  1. use os.pipe to share result between the two processes 使用os.pipe在两个进程之间共享结果

  2. use os.dup2 instead of changing sys.stdout 使用os.dup2而不是更改sys.stdout

A demo example as following shown 演示示例如下所示

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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM