mocking subprocess.Popen

2019-08-17 17:31发布

问题:

I have a module utils.py which has this run_cmd() method

def run_cmd(cmd):
    pipe = subprocess.Popen(cmd,
                            shell=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    print(pipe.communicate())
    print(pipe.returncode)
    stdout, stderr = [stream.strip() for stream in pipe.communicate()]
    output = ' - STDOUT: "%s"' % stdout if len(stdout) > 0 else ''
    error = ' - STDERR: "%s"' % stdout if len(stderr) > 0 else ''
    logger.debug("Running [{command}] returns: [{rc}]{output}{error}".format(
                 command=cmd,
                 rc=pipe.returncode,
                 output=output,
                 error=error))

    return pipe.returncode, stdout, stderr

I wrote a unit-test using mock and this link stackoverflow as a reference

  @patch('subprocess.Popen')
  @patch('utils.logger.debug')
  def test_run_cmd(self, mock_popen, mock_log):
    cmd = 'mock_command'
    mocked_pipe = Mock()
    attrs = {'communicate.return_value': ('output', 'error'), 'returncode': 0}
    mocked_pipe.configure_mock(**attrs)
    mock_popen.return_value = mocked_pipe
    log_calls = [call('Running [mock_command] returns: [0]outputerror')]
    utils.run_cmd(cmd)
    mock_popen.assert_called_once_with(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    mock_log.assert_has_calls(log_calls)

I got this as output when I run the nosetest

        stdout, stderr = [stream.strip() for stream in pipe.communicate()]
ValueError: need more than 0 values to unpack
-------------------- >> begin captured stdout << ---------------------
<MagicMock name='Popen().communicate()' id='140197276165008'>
<MagicMock name='Popen().returncode' id='140197276242512'>

--------------------- >> end captured stdout << ----------------------
FAILED (errors=1)

Why is the pipe.communicate() not printing the ('output', 'error') or the pipe.returncode not printing the 0, but their mock methods? Where did it go wrong? How could I solve this?

回答1:

Ah, You already have an answer of your question. look closely and you will know why. You have to mock like you did for logger. You forgot to mention utils while creating mocked object.

@patch('utils.subprocess.Popen')

Now, mocking the nested function with multiple values, I think you should look at the side_effects and here.

I haven't tested the below code but I hope it should work or at least give you some sort of lead.

mocked_open.return_value.communicate.return_value = ('output', 'error')
mocked_open.return_value.returncode = 0

Hope that will help!