在 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