【问题标题】:Creating a decorator to mock input() using monkeypatch in pytest在pytest中使用monkeypatch创建一个装饰器来模拟input()
【发布时间】:2022-11-11 02:25:59
【问题描述】:

最终目标:我希望能够在 pytest 中快速模拟 input() 内置函数,并将其替换为生成(变量)字符串列表的迭代器。这是我当前的版本,有效:

from typing import Callable
import pytest


def _create_patched_input(str_list: list[str]) -> Callable:
    str_iter = iter(str_list.copy())

    def patched_input(prompt: str) -> str:  # has the same signature as input
        val = next(str_iter)
        print(prompt + val, end="\n"),
        return val

    return patched_input


@pytest.fixture
def _mock_input(monkeypatch, input_string_list: list[str]):
    patched_input = _create_patched_input(input_string_list)
    monkeypatch.setattr("builtins.input", patched_input)


def mock_input(f):
    return pytest.mark.usefixtures("_mock_input")(f)

# Beginning of test code
def get_name(prompt: str) -> str:
    return input(prompt)


@mock_input
@pytest.mark.parametrize(
    "input_string_list",
    (["Alice", "Bob", "Carol"], ["Dale", "Evie", "Frank", "George"]),
)
def test_get_name(input_string_list):
    for name in input_string_list:
        assert get_name("What is your name?") == name

但是,由于以下几个原因,这感觉不完整:

  • 要求parameterize调用中的参数名称为input_string_list,感觉比较脆。
  • 如果我将灯具移动到另一个函数中,我需要同时导入mock_input_mock_input

对我来说正确的是拥有一个可以像@mock_input(strings)一样使用的装饰器(工厂),这样你就可以像这样使用它

@mock_input(["Alice", "Bob", "Carol"])
def test_get_name():
    ....

或者,更符合我的用例,

@pytest.mark.parametrize(
    "input_list", # can be named whatever
    (["Alice", "Bob", "Carol"], ["Dale", "Evie", "Frank", "George"]),
)
@mock_input(input_list)
def test_get_name():
    ....

后者我认为您做不到,因为 pytest 不会将其识别为夹具。最好的方法是什么?

【问题讨论】:

  • 我宁愿在_mock_input 夹具上使用间接参数化,因为它无论如何都需要参数。
  • @hoefling 你能说明你的意思吗?

标签: python mocking pytest decorator


【解决方案1】:

我会对mock_input 使用间接参数化,因为它在没有接收参数的情况下无法工作。另外,我会将mock_input 重构为一个固定装置,它确实传递它接收到的参数,并在途中执行模拟。例如,当使用unittest.mock.patch() 时:

import pytest
from unittest.mock import patch

@pytest.fixture
def inputs(request):
    with patch('builtins.input', side_effect=request.param):
        yield request.param

或者,如果你想使用monkeypatch,代码会变得有点复杂:

@pytest.fixture
def inputs(monkeypatch, request):
    it = iter(request.param)

    def fake_input(prefix):
        return next(it)

    monkeypatch.setattr('builtins.input', fake_input)
    yield request.param

现在使用inputs 作为测试参数并间接对其进行参数化:

@pytest.mark.parametrize(
    'inputs',
    (["Alice", "Bob", "Carol"], ["Dale", "Evie", "Frank", "George"]),
    indirect=True
)
def test_get_name(inputs):
    for name in inputs:
        assert get_name("What is your name?") == name

【讨论】:

    猜你喜欢
    • 2018-12-30
    • 2016-08-17
    • 2020-07-30
    • 2020-09-20
    • 2019-02-03
    • 2017-11-16
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    相关资源
    最近更新 更多