Python:带有来自不同模块的模拟功能的单位测试
#python #测试 #mock

我最近开始学习Python 3和pytestunittest的单元测试。

当我努力弄清楚如何在几种情况下嘲笑时,我在这里注意到,以便任何人都有相同的问题可能会觉得这很有用。

结构和代码

在编写测试之前,这是我的文件夹和文件结构。

src/
    ├── my.py  
    ├── my_modules/
    │   ├── __init__.py
    │   └── util.py
    └── tests/
        ├── __init__.py
        ├── test_my.py
        └── test_unit.py

my.py

from my_modules.util import util

def main():
    return util('my input')

if __name__ == '__main__':
    main()

util.py

from datetime import datetime

def util(input: str) -> str:
    input = add_time(input)
    return f"util: {input}"

def add_time(input: str) -> str:
    return f"{datetime.now()}: {input}"

为UTIT功能添加单元测试

对于util方法,我需要嘲笑来自同一文件的add_time。我找到了几种实现这一目标的方法,这就是其中之一。

test_unit.py

from unittest.mock import patch, Mock
from my_modules.util import util

@patch('my_modules.util.add_time', Mock(return_value='dummy'))
def test_util():
    expected = 'util: dummy'
    input = 'input'
    input = util(input)
    assert expected == input

我将unittest.mock.patch函数用作装饰器,并将所需的返回值指定为Mock对象的一部分。 add_time的名称空间与在同一模块中的util函数相同。

为add_time函数添加单元测试

要进行单元测试add_time函数,我需要模拟datetime.now()函数。我再次使用unittest.mock.patch。这次,我需要创建具有更多代码的Mock,因为我需要模拟一个函数,而不是简单的return_value或属性。

from datetime import datetime
from unittest.mock import patch, Mock
from my_modules.util import add_time

@patch('my_modules.util.datetime', Mock(**{"now.return_value": datetime(2023, 1, 1)}))
def test_add_time():
    expected = f'{datetime(2023, 1, 1)}: input'
    input = 'input'
    input = add_time(input)
    assert expected == input

i可以将包含属性和方法信息的字典传递给Mock对象。当我模拟now功能时,我使用"now.return_value":<some date>

如果它是一个属性,我可以将其像Mock(attribute_name=<value>)一样传递给Mock,也可以作为词典的一部分,例如{"attribute":<value>}

我需要指定my_modules.util.datetime,模块名称有点有趣。原因是,一旦将datetime进口到util.py,它就会成为同一模块的一部分,这使我感到困惑。

我可以在下一个测试中显示另一个样本。

为主功能添加单元测试

要测试my.py中的main方法,我只需要模拟util方法即可。让我们去做吧。

test_my.py

from my import main
from unittest.mock import patch, Mock

@patch("my.util", Mock(return_value="dummy"))
def test_main():
    result = main()
    assert result =='dummy'

即使util函数来自my_modules模块,在测试时间中,它也会像我先前解释的那样变为my.util名称空间。

i在装饰器中指定Mock对象,但是如果实际上调用了带有预期参数的util函数,则不能。因此,让我们接受测试功能中的模拟。

@patch("my.util")
def test_main_util_called_with_expected_parameter(util_mock):
    util_mock.return_value = 'dummy'
    result = main()
    assert result =='dummy'
    util_mock.assert_any_call('my input')

这次,我使用装饰器而无需传递Mock对象。相反,我在参数中以util_mock的方式收到了它,然后指定了return_value

当我可以访问模拟时,我可以断言该方法是用预期参数调用的。

最后,我还了解到我也可以使用with语句来实现相同的功能。

def test_main_util_called_with_expected_parameter_with():
    with patch("my.util") as util_mock:
        util_mock.return_value = 'dummy'
        result = main()
    assert result =='dummy'
    util_mock.assert_any_call('my input')

这样做,我不需要装饰器。

所有测试成功运行!

test results

概括

我实际上还不知道哪种是模拟功能的最佳方法。我相信每个人都有利弊,因此,如果你们有任何建议或意见,请在评论中告诉我!谢谢。