我最近开始学习Python 3和pytest
和unittest
的单元测试。
当我努力弄清楚如何在几种情况下嘲笑时,我在这里注意到,以便任何人都有相同的问题可能会觉得这很有用。
结构和代码
在编写测试之前,这是我的文件夹和文件结构。
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')
这样做,我不需要装饰器。
所有测试成功运行!
概括
我实际上还不知道哪种是模拟功能的最佳方法。我相信每个人都有利弊,因此,如果你们有任何建议或意见,请在评论中告诉我!谢谢。