在 Pytest 中处理 MagicMock 转换
Dealing with MagicMock Conversions in Pytest
上下文是我正在尝试对一些涉及要检查的 Python 版本的 Python 代码进行单元测试。我在 platform
方法下工作,但有人告诉我使用 platform
不是一个好主意,我应该改用 sys.version_info
。因此我有以下逻辑:
def main() -> None:
python_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
if float(python_version) < 3.7:
sys.stderr.write("\nQuendor requires Python 3.7 or later.\n")
sys.stderr.write(f"Your current version is {python_version}\n\n")
sys.exit(1)
(我知道我也可以为我的支票做 if sys.version_info < (3, 7):
。)
我的单元测试是这样的:
def test_bad_python_version(capsys) -> None:
import sys
from quendor.__main__ import main
with mock.patch.object(sys, "version_info") as v_info:
v_info.major = 3
v_info.minor = 5
main()
terminal_text = capsys.readouterr()
print(terminal_text)
当然,我的测试还没有完成。我正在尝试打印输出以确保测试正常进行。
运行 那个测试让我得到这个:
ValueError: could not convert string to float:
"<MagicMock name='version_info.__getitem__()'
id='1417904973808'>.<MagicMock name='version_info.__getitem__()'
id='1417904973808'>"
顺便说一句,如果我将 main
中的条件切换到此 (if sys.version_info < (3, 7):
),我会得到一个类似但不同的错误:
TypeError: '<' not supported between instances of 'MagicMock' and 'tuple'
我不确定如何正确传递 version_info,我认为这是我的问题。我搜索了文档,我在这里看到了其他关于 MagicMock 的问题(例如:),但没有任何明确说明:“这就是如何做到这一点。”
我应该注意到我有一个版本在使用 platform
时运行得非常好。我的测试版本如下所示:
with mock.patch.object(
platform,
"python_version",
return_value="3.5",
), pytest.raises(SystemExit) as pytest_wrapped_e:
main()
terminal_text = capsys.readouterr()
expect(terminal_text.err).to(contain("Quendor requires Python 3.7"))
关键似乎是我使用的“return_value”。
在你的单元测试中你是 setting/mocking 版本
v_info.major = 3
v_info.minor = 5
但正在访问 sys.version_info[0]
或 sys.version_info[1]
。
尝试将 main()
中的版本提取更改为
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
为我工作:
In [8]: with mock.patch.object(sys, "version_info") as v_info:
...: v_info.major = 3
...: v_info.minor = 5
...: main()
...:
Quendor requires Python 3.7 or later.
Your current version is 3.5
你是这样修补的:
with mock.patch.object(sys, "version_info") as v_info:
v_info.major = 3
v_info.minor = 5
因此,它将应用于以下调用:
sys.version_info.major # Will display 3
sys.version_info.minor # Will display 5
但是,您在源代码中访问的内容却不同:
sys.version_info
sys.version_info[0]
sys.version_info[1]
显然,您的补丁不会反映出来,因为它们是针对 .major
和 .minor
的,但无论如何都不会调用它们。相反,更改要应用于 sys.version_info
的补丁,它应该 return 项目的元组 (3, 5)
以便它会反映出来,因为它是在您的源代码中调用的那个。无需更改源代码中的任何内容:
src.py
import sys
def main() -> None:
print("Call main")
python_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
if float(python_version) < 3.7:
print("Quendor requires Python 3.7 or later.")
def main2() -> None:
print("Call main2")
if sys.version_info < (3, 7):
print("Quendor requires Python 3.7 or later.")
test_src.py
import sys
from unittest import mock
from src import main, main2
def test_bad_python_version():
with mock.patch.object(sys, "version_info", (3, 5)) as v_info:
main()
main2()
def test_good_python_version():
with mock.patch.object(sys, "version_info", (3, 8)) as v_info:
main()
main2()
输出
$ pytest -q -rP
.. [100%]
============================================== PASSES ===============================================
______________________________________ test_bad_python_version ______________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Call main
Quendor requires Python 3.7 or later.
Call main2
Quendor requires Python 3.7 or later.
_____________________________________ test_good_python_version ______________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Call main
Call main2
2 passed in 0.06s
上下文是我正在尝试对一些涉及要检查的 Python 版本的 Python 代码进行单元测试。我在 platform
方法下工作,但有人告诉我使用 platform
不是一个好主意,我应该改用 sys.version_info
。因此我有以下逻辑:
def main() -> None:
python_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
if float(python_version) < 3.7:
sys.stderr.write("\nQuendor requires Python 3.7 or later.\n")
sys.stderr.write(f"Your current version is {python_version}\n\n")
sys.exit(1)
(我知道我也可以为我的支票做 if sys.version_info < (3, 7):
。)
我的单元测试是这样的:
def test_bad_python_version(capsys) -> None:
import sys
from quendor.__main__ import main
with mock.patch.object(sys, "version_info") as v_info:
v_info.major = 3
v_info.minor = 5
main()
terminal_text = capsys.readouterr()
print(terminal_text)
当然,我的测试还没有完成。我正在尝试打印输出以确保测试正常进行。
运行 那个测试让我得到这个:
ValueError: could not convert string to float:
"<MagicMock name='version_info.__getitem__()'
id='1417904973808'>.<MagicMock name='version_info.__getitem__()'
id='1417904973808'>"
顺便说一句,如果我将 main
中的条件切换到此 (if sys.version_info < (3, 7):
),我会得到一个类似但不同的错误:
TypeError: '<' not supported between instances of 'MagicMock' and 'tuple'
我不确定如何正确传递 version_info,我认为这是我的问题。我搜索了文档,我在这里看到了其他关于 MagicMock 的问题(例如:
我应该注意到我有一个版本在使用 platform
时运行得非常好。我的测试版本如下所示:
with mock.patch.object(
platform,
"python_version",
return_value="3.5",
), pytest.raises(SystemExit) as pytest_wrapped_e:
main()
terminal_text = capsys.readouterr()
expect(terminal_text.err).to(contain("Quendor requires Python 3.7"))
关键似乎是我使用的“return_value”。
在你的单元测试中你是 setting/mocking 版本
v_info.major = 3
v_info.minor = 5
但正在访问 sys.version_info[0]
或 sys.version_info[1]
。
尝试将 main()
中的版本提取更改为
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
为我工作:
In [8]: with mock.patch.object(sys, "version_info") as v_info:
...: v_info.major = 3
...: v_info.minor = 5
...: main()
...:
Quendor requires Python 3.7 or later.
Your current version is 3.5
你是这样修补的:
with mock.patch.object(sys, "version_info") as v_info:
v_info.major = 3
v_info.minor = 5
因此,它将应用于以下调用:
sys.version_info.major # Will display 3
sys.version_info.minor # Will display 5
但是,您在源代码中访问的内容却不同:
sys.version_info
sys.version_info[0]
sys.version_info[1]
显然,您的补丁不会反映出来,因为它们是针对 .major
和 .minor
的,但无论如何都不会调用它们。相反,更改要应用于 sys.version_info
的补丁,它应该 return 项目的元组 (3, 5)
以便它会反映出来,因为它是在您的源代码中调用的那个。无需更改源代码中的任何内容:
src.py
import sys
def main() -> None:
print("Call main")
python_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
if float(python_version) < 3.7:
print("Quendor requires Python 3.7 or later.")
def main2() -> None:
print("Call main2")
if sys.version_info < (3, 7):
print("Quendor requires Python 3.7 or later.")
test_src.py
import sys
from unittest import mock
from src import main, main2
def test_bad_python_version():
with mock.patch.object(sys, "version_info", (3, 5)) as v_info:
main()
main2()
def test_good_python_version():
with mock.patch.object(sys, "version_info", (3, 8)) as v_info:
main()
main2()
输出
$ pytest -q -rP
.. [100%]
============================================== PASSES ===============================================
______________________________________ test_bad_python_version ______________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Call main
Quendor requires Python 3.7 or later.
Call main2
Quendor requires Python 3.7 or later.
_____________________________________ test_good_python_version ______________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Call main
Call main2
2 passed in 0.06s