如何为多个测试修补一个函数

How to monkey patch a function for multiple tests

考虑我的模块"mymodule.py"

# contents of "mymodule.py"

def func1(x):
    return x * 2

我想模拟这个函数并改变它 return。根据文档,我可以这样做:

# contents of "test_mymodule.py"

import mymodule
import pytest

@pytest.fixture
def mock_func1():
    def mock_ret(*args, **kwargs):
        return 2

def test_func1_a(monkeypatch, mock_func1):
    monkeypatch.setattr(mymodule, "func1", mock_func1)
    assert mymodule.func1(1) == 2 

def test_func1_b(monkeypatch, mock_func1):
    monkeypatch.setattr(mymodule, "func1", mock_func1)
    assert mymodule.func1(1) != 37 

但是,我不想为每个测试都修改模块。对于整个测试模块 test_mymodule.py 的范围,一次 monkeypatch.setattr 的正确方法是什么?

我期待这样的事情

# contents of "test_mymodule.py"

import mymodule
import pytest

@pytest.fixture
def mock_func1():
    def mock_ret(*args, **kwargs):
        return 2

monkeypatch.setattr(mymodule, "func1", mock_func1)

def test_func1_a():
    assert mymodule.func1(1) == 2 

def test_func1_b():
    assert mymodule.func1(1) != 37 

但这让我感动

NameError: name 'monkeypatch' is not defined

直接从pytest偷来的:

import mymodule
import pytest

def wildpatch(target, name, value=None, raising=True):
    import inspect

    if value is None:
        if not isinstance(target, _basestring):
            raise TypeError("use setattr(target, name, value) or "
                            "setattr(target, value) with target being a dotted "
                            "import string")
        value = name
        name, target = derive_importpath(target, raising)

    oldval = getattr(target, name, None)
    if raising and oldval is None:
        raise AttributeError("%r has no attribute %r" % (target, name))

    # avoid class descriptors like staticmethod/classmethod
    if inspect.isclass(target):
        oldval = target.__dict__.get(name, None)
    setattr(target, name, value)


##@pytest.fixture
##def mock_func1():
##    def mock_ret(*args, **kwargs):
##        print("monkeypatched func1")
##        return 2

def mock_func1(*args, **kwargs):
    print("monkeypatched func1")
    return 2 

wildpatch(mymodule, "func1", mock_func1)

def test_func1_a():
    print("Running test_func1_a")
    assert mymodule.func1(1) == 2 

def test_func1_b():
    assert mymodule.func1(1) != 37

在 运行 python -m pytest -s test.py 上产生

=============================== test session starts ================
platform linux -- Python 3.4.3, pytest-3.1.2, py-1.4.34, pluggy-0.4.0
rootdir: /tmp/ab, inifile:
collected 2 items 

test.py Running test_func1_a
monkeypatched func1
.monkeypatched func1
.

=========================== 2 passed in 0.01 seconds ===============================

我猜你想要的只是将 func1 重定向到你自己的函数。

刚刚看到这个答案是因为我正在尝试做类似的事情。您可以使用这样的装饰器来对测试进行预处理。可以在 mock_func_1_in_test 装饰器下方添加其他 pytest 装饰器。

# contents of "test_mymodule.py"

import mymodule
import pytest

@pytest.fixture
def mock_func1():
    def mock_ret(*args, **kwargs):
        return 2

    return mock_ret

def mock_func_1_in_test(func):
    def inner(monkeypatch, mock_func1, *args, **kwargs):
        monkeypatch.setattr(mymodule, "func1", mock_func1)
        return func(*args, **kwargs)
    return inner

@mock_func_1_in_test
def test_func1_a():
    assert mymodule.func1(1) == 2 

@mock_func_1_in_test
def test_func1_b():
    assert mymodule.func1(1) != 37 

这如您所料:

$ pytest
================================== test session starts ===================================
platform darwin -- Python 3.6.6, pytest-3.6.0, py-1.6.0, pluggy-0.6.0
rootdir: /Users/delgadom/git/messin/pytest_test, inifile:
plugins: cov-2.5.1
collected 2 items

test_mymodule.py ..                                                                [100%]

================================ 2 passed in 0.03 seconds ================================

这就是我所做的,以保持事物稍微干净。只需定义一个 pytest fixture 来修补 conftest.py 中的模块。但是,这个夹具仍然需要传递给我们的每个测试。

conftest.py

的内容
import mymodule
import pytest


@pytest.fixture(autouse=True)
def patch_mymodule(monkeypatch):
    def mock_func1(*args, **kwargs):
        return 2
    monkeypatch.setattr(mymodule, "func1", mock_func1)

“mymodule.py”的内容

def func1(x):
    return x * 2

“test_mymodule.py”的内容

import mymodule
import pytest


def test_func1_a(patch_mymodule):
    # you can pass anything to the function below, 1 is just some arbitrary choice
    assert mymodule.func1(1) == 2

def test_func1_b(patch_mymodule):
    assert mymodule.func1(1) != 37 

只需将 autouse=True 作为参数添加到 fixture decorator.

@pytest.fixture(autouse=True)
def mock_func1():
    def mock_ret(*args, **kwargs):
        return 2