模拟python subprocess.call函数并捕获其系统退出代码

时间:2018-01-07 13:23:36

标签: python unit-testing mocking subprocess

编写测试用例来处理成功和失败的python子进程调用,我需要捕获subprocess.call返回代码。

使用python unittest.mock module,是否可以修补subprocess.call函数并捕获其实际系统退出代码?

考虑使用以下代码的外部库:

## <somemodule.py> file

import subprocess


def run_hook(cmd):
    # ...
    subprocess.call(cmd, shell=True)
    sum_one = 1+1
    return sum_one

我无法修改函数run_hook。它不是我的代码的一部分。但是,事实是subprocess.call在其他陈述中被调用。

这里我们有一段代码返回强制系统退出错误代码1:

## <somemodule.py> tests file

import subprocess
from somemodule import run_hook

try:
    from unittest.mock import patch
except ImportError:
    from mock import patch


@patch("subprocess.call", side_effect=subprocess.call)
def test_run_hook_systemexit_not_0(call_mock):
    python_exec = sys.executable
    cmd_parts = [python_exec, "-c", "'exit(1)'"] # Forced error code 1
    cmd = " ".join(cmd_parts)
    run_hook(cmd)
    call_mock.assert_called_once_with(cmd, shell=True)

    # I need to change the following assertion to
    # catch real return code "1"

    assert "1" == call_mock.return_value(), \
           "Forced system exit(1) should return 1. Just for example purpose"

如何改进此测试以捕获任何subprocess.call返回码的预期实际值?

出于兼容性目的,无法使用新的subprocess.run(3.5+)。这个库仍然被python 2.7+环境广泛使用。

2 个答案:

答案 0 :(得分:0)

关于subprocess.call,文档说:

  

运行 args 描述的命令。等待命令完成,然后返回 returncode 属性。

您需要做的就是修改VAR(Zoo, type="both",lag.max = 10,ic="AIC") 函数并返回退出代码:

run_hook

这只是您的测试代码。

def run_hook(cmd):
    # ...
    return subprocess.call(cmd, shell=True)

我的建议:改为使用def test_run_hook_systemexit_not_0(): python_exec = sys.executable args = [python_exec, "-c", "'exit(1)'"] assert run_hook(args) == 1

修改

如果您想查看subprocess.run的退出代码,则需要使用自己的版本进行修补,如下所示:

subprocess.call

然后,您使用import subprocess _original_call = subprocess.call def assert_call(*args, **kwargs): assert _original_call(*args, **kwargs) == 0 作为补丁的副作用函数:

assert_call

答案 1 :(得分:0)

围绕subprocess.call的包装器可以处理断言验证。

在这种情况下,我将此包装器声明为side_effect定义中的@patch参数。

在这种情况下,以下实施方法运作良好。

import sys
import unittest


try:
    from unittest.mock import patch
except ImportError:
    from mock import patch


def subprocess_call_assert_wrap(expected, message=None):
    from subprocess import call as _subcall
    def _wrapped(*args, **kwargs):
        if message:
            assert expected == _subcall(*args, **kwargs), message
        else:
            assert expected == _subcall(*args, **kwargs)
    return _wrapped


class TestCallIsBeingCalled(unittest.TestCase):

    @patch("subprocess.call", side_effect=subprocess_call_assert_wrap(expected=0))
    def test_run_hook_systemexit_0(self, call_mock):
        python_exec = sys.executable
        cmd_parts = [python_exec, "-c", "'exit(0)'"]
        cmd = " ".join(cmd_parts)
        run_hook(cmd)
        call_mock.assert_called_once_with(cmd, shell=True)

    @patch("subprocess.call", side_effect=subprocess_call_assert_wrap(expected=1))
    def test_run_hook_systemexit_not_0(self, call_mock):
        python_exec = sys.executable
        cmd_parts = [python_exec, "-c", "'exit(1)'"]
        cmd = " ".join(cmd_parts)
        run_hook(cmd)
        call_mock.assert_called_once_with(cmd, shell=True)

采用这种方法进行一些测试后,似乎可以用于更通用的调用,例如:

def assert_wrapper(expected, callable, message=None):
    def _wrapped(*args, **kwargs):
        if message:
            assert expected == callable(*args, **kwargs), message
        else:
            assert expected == callable(*args, **kwargs)
    return _wrapped

这不是最好的方法,但似乎是合理的。

有一些最知名的lib具有类似的行为,我可以在这个项目中使用吗?