如何使用装饰器修补 sys 属性?
How do I patch a sys attribute using a decorator?
我有一个函数取决于 Python 版本。我想通过模拟 sys.version
信息在单元测试中对此进行测试。以下代码有效:
def test_python_version(self):
with patch("sys.version_info", [3]):
my_func()
with patch("sys.version_info", [2]):
my_func()
我想使用装饰器,但下面的代码不起作用。为什么是这样?如何设置传递到我的测试中的 MagicMock 对象的值?谢谢
@patch("sys.version_info")
def test_python_version(self, mock_version_info):
mock_version_info.return_value = [3]
my_func()
mock_version_info.return_value = [2]
my_func()
当 my_func
尝试执行 if sys.version_info[0] < 3
时,TypeError: '<' not supported between instances of 'MagicMock' and 'int'
失败。
编辑:
是的,它有效。原始函数 returns 具有 major
、minor
和 micro
字段的命名元组。您可以通过构建自己的命名元组来模仿它。当您使用 int
索引访问时,我只是使用一个简单的元组。您的代码的问题在于您使用 [0]
编制索引的方式不正确。
编辑2:
正如 Zaur Nasibov 指出的那样,sys.version_info
不是一个函数,所以我的代码是错误的,尽管使用模拟看起来很好(正如 GenError 也发现的那样)。我做了一些小改动来修复它(请参阅 GenError 答案以获取使用 PropertyMock
的替代方法)
import sys
from unittest.mock import patch
def my_func():
version = sys.version_info # removed the ()
print('Detected version:', version)
if version[0] < 3:
print('Error: Python 3 required.')
@patch('__main__.sys')
def test_python_version(mock_sys):
mock_sys.version_info = (3,6,2)
my_func()
print()
mock_sys.version_info = (2,7,0)
my_func()
test_python_version()
产出
Detected version: (3, 6, 2)
Detected version: (2, 7, 0)
Error: Python 3 required.
我通过 progmatico 找到了答案,但它对我来说有一个严重的问题,因为它需要调用 sys.version_info()
而不是 sys.version_info
如果 运行 通常会破坏代码。
示例:
#filename: a.py
import sys
def do_something():
if sys.version_info > (3,5):
print('Python 3.5 or newer')
else:
print('Python pre 3.5')
现在,如果我想在单元测试中测试这两种情况,@patch("sys.version_info")
将导致 OP 给出的错误。如果不使用模拟,将 do_something()
更改为使用 sys.version_info()
会破坏它。
测试文件:
#filename: test_a.py
from unittest.mock import patch, PropertyMock
import a
@patch('a.sys')
def test_a(mock_sys):
type(mock_sys).version_info = PropertyMock(return_value=(3,4))
a.do_something() # will show mocked python version
所以你必须mock模块a中导入的sys模块,并在其上设置一个PropertyMock
我有一个函数取决于 Python 版本。我想通过模拟 sys.version
信息在单元测试中对此进行测试。以下代码有效:
def test_python_version(self):
with patch("sys.version_info", [3]):
my_func()
with patch("sys.version_info", [2]):
my_func()
我想使用装饰器,但下面的代码不起作用。为什么是这样?如何设置传递到我的测试中的 MagicMock 对象的值?谢谢
@patch("sys.version_info")
def test_python_version(self, mock_version_info):
mock_version_info.return_value = [3]
my_func()
mock_version_info.return_value = [2]
my_func()
当 my_func
尝试执行 if sys.version_info[0] < 3
时,TypeError: '<' not supported between instances of 'MagicMock' and 'int'
失败。
编辑:
是的,它有效。原始函数 returns 具有 major
、minor
和 micro
字段的命名元组。您可以通过构建自己的命名元组来模仿它。当您使用 int
索引访问时,我只是使用一个简单的元组。您的代码的问题在于您使用 [0]
编制索引的方式不正确。
编辑2:
正如 Zaur Nasibov 指出的那样,sys.version_info
不是一个函数,所以我的代码是错误的,尽管使用模拟看起来很好(正如 GenError 也发现的那样)。我做了一些小改动来修复它(请参阅 GenError 答案以获取使用 PropertyMock
的替代方法)
import sys
from unittest.mock import patch
def my_func():
version = sys.version_info # removed the ()
print('Detected version:', version)
if version[0] < 3:
print('Error: Python 3 required.')
@patch('__main__.sys')
def test_python_version(mock_sys):
mock_sys.version_info = (3,6,2)
my_func()
print()
mock_sys.version_info = (2,7,0)
my_func()
test_python_version()
产出
Detected version: (3, 6, 2)
Detected version: (2, 7, 0)
Error: Python 3 required.
我通过 progmatico 找到了答案,但它对我来说有一个严重的问题,因为它需要调用 sys.version_info()
而不是 sys.version_info
如果 运行 通常会破坏代码。
示例:
#filename: a.py
import sys
def do_something():
if sys.version_info > (3,5):
print('Python 3.5 or newer')
else:
print('Python pre 3.5')
现在,如果我想在单元测试中测试这两种情况,@patch("sys.version_info")
将导致 OP 给出的错误。如果不使用模拟,将 do_something()
更改为使用 sys.version_info()
会破坏它。
测试文件:
#filename: test_a.py
from unittest.mock import patch, PropertyMock
import a
@patch('a.sys')
def test_a(mock_sys):
type(mock_sys).version_info = PropertyMock(return_value=(3,4))
a.do_something() # will show mocked python version
所以你必须mock模块a中导入的sys模块,并在其上设置一个PropertyMock