在 Python 中模拟子进程调用

新手上路,请多包涵

我有一个方法 - run_script() - 我想测试一下。具体来说,我想测试对 subprocess.Popen 的调用。测试使用特定参数调用 subprocess.Popen 会更好。然而,当我运行测试时,我得到 TypeError: 'tuple' object is not callable

我如何测试我的方法以确保 subprocess 实际上是使用模拟调用的?

 @mock.patch("subprocess.Popen")
def run_script(file_path):
    process = subprocess.Popen(["myscript", -M, file_path], stdout=subprocess.PIPE)
    output, err = process.communicate()
    return process.returncode

def test_run_script(self, mock_subproc_popen):
    mock_subproc_popen.return_value = mock.Mock(
        communicate=("ouput", "error"), returncode=0
    )
    am.account_manager("path")
    self.assertTrue(mock_subproc_popen.called)

原文由 steve 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 849
2 个回答

在我看来,你在 run_script 函数上使用补丁装饰器似乎很不寻常,因为你没有在那里传递模拟参数。

这个怎么样:

 from unittest import mock
import subprocess

def run_script(file_path):
    process = subprocess.Popen(["myscript", -M, file_path], stdout=subprocess.PIPE)
    output, err = process.communicate()
    return process.returncode

@mock.patch("subprocess.Popen")
def test_run_script(self, mock_subproc_popen):
    process_mock = mock.Mock()
    attrs = {"communicate.return_value": ("output", "error")}
    process_mock.configure_mock(**attrs)
    mock_subproc_popen.return_value = process_mock
    am.account_manager("path")  # this calls run_script somewhere, is that right?
    self.assertTrue(mock_subproc_popen.called)

现在,您模拟的 subprocess.Popen 似乎返回一个元组,导致 process.communicate() 引发 TypeError: 'tuple' object is not callable. 。因此,最重要的是让 mock_subproc_popen 的 return_value 恰到好处。

原文由 PawelP 发布,翻译遵循 CC BY-SA 4.0 许可协议

如果要检查模拟对象是否使用特定参数调用,可以将 side_effect 参数添加到 mock.patch 装饰器。

side_effect 函数的返回值决定了 subprocess.Popen 的返回值。如果 side_effect_func 返回默认值, subprocess.Popen 将以正常方式调用。

 from unittest import mock, TestCase
from unittest.mock import DEFAULT
import subprocess

def run_script(script_path, my_arg):
    process = subprocess.Popen([script_path, my_arg])
    return process

def side_effect_func(*args, **kwargs):

    # Print the arguments
    print(args)

    # If 'bar' is contained within the arguments, return 'foo'
    if any(['bar' in arg for arg in args]):
        return 'foo'

    # If 'bar' is not contained within the arguments, run subprocess.Popen
    else:
        return DEFAULT

class TestRunScriptClass(TestCase):

    @mock.patch("subprocess.Popen", side_effect=side_effect_func)
    def test_run_script(self, mock):
        # Run the function
        process = run_script(script_path='my_script.py', my_arg='bar')

        # Assert if the mock object was called
        self.assertTrue(mock.called)

        # Assert if the mock object returned 'foo' when providing 'bar'
        self.assertEqual(process, 'foo')

原文由 Helge Schneider 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题