模拟os.system进行单元测试的最佳方法是什么(PyTest)

时间:2017-09-15 11:31:01

标签: python unit-testing mocking pytest

我有一个Python脚本可以执行多个os.system调用。将这一系列作为字符串列表断言将很容易(而且相对优雅)。

拦截(和阻止)实际通话并不容易。在相关的脚本中,我可以像这样在SUT(*)中抽象os.system:

os_system = None

def main():
    return do_the_thing(os.system)

def do_the_thing(os_sys):
    global os_system
    os_system = os_sys

    # all other function should use os_system instead of os.system

我的测试当然会调用my_script.do_the_thing()而不是my_script.main()(留下少量未经测试的代码)。

备用选项:我可以保持SUT不变,并在调用SUT中的os.system之前在测试方法中全局替换main()

这给我带来了新的问题,因为这是一个全球性和持久的变化。很好,所以我在同一个测试方法中使用try/finally,并在离开测试方法之前替换原始版本。无论测试方法是通过还是失败,它都能正常工作。

是否有一种安全且优雅以设置/拆解为中心的方式为PyTest执行此操作?

其他并发症:我想对stdout和stderr做同样的事情。是的,它确实是我正在测试的main()脚本。

  • SUT ==被测系统

2 个答案:

答案 0 :(得分:3)

Python 3(> = 3.3)标准库在官方文档中有great tutorial about Mock。对于Python 2,您可以在PyPi上使用反向移植的库:Mock

以下是一个示例用法。假设您要在此函数中模拟对os.system的调用:

import os


def my_function(src_dir):
    os.system('ls ' + src_dir)

为此,您可以使用unittest.mock.patch装饰器,如下所示:

import unittest.mock


@unittest.mock.patch('os.system')
def test_my_function(os_system):
    # type: (unittest.mock.Mock) -> None
    my_function("/path/to/dir")
    os_system.assert_called_once_with('ls /path/to/dir')

此测试函数将在执行期间修补os.system调用。 os.system最后会恢复。

然后,有几个"断言"检查调用,参数和结果的方法。您还可以检查在某些情况下是否会引发异常。

答案 1 :(得分:1)

只想添加一个重要的细节。

如果您的代码使用import system这样的例子:

myls.py

import os


def do_ls():
    os.system('ls')

然后测试中的补丁应如下所示:

test_myls.py

from unittest.mock import patch


@patch('os.system')
def test_do_ls(mock_system):
    do_ls()
    mock_system.assert_called()

但是,如果代码使用from os import system,例如像这样:

myls.py

from os import system


def do_ls():
    system('ls')

然后测试中的补丁应如下所示:

test_myls.py

from unittest.mock import patch


@patch('myls.system')
def test_do_ls(mock_system):
    do_ls()
    mock_system.assert_called()

这让我有些困惑,因为我忘记了按原本打算阅读where to patch上的这一节。如果修补程序似乎不起作用,那么这是需要研究的重点之一。