首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python monkeypatching最佳实践

Python monkeypatching最佳实践
EN

Stack Overflow用户
提问于 2018-07-18 21:46:30
回答 1查看 1.3K关注 0票数 2

我正在测试一个具有多个外部依赖项的应用程序,并且我已经使用monkeypatching技术通过自定义实现来修补外部库的函数,以帮助我的测试。它的工作方式与预期一致。

但我目前遇到的问题是,这让我的测试文件变得非常混乱。我有几个测试,每个测试都需要自己的补丁函数实现。

例如,假设我有一个来自外部库的GET函数,我的test_a()需要修补GET()以便返回False,而test_b()需要修补GET()以便返回True。

处理这种情况的首选方法是什么?目前,我做了以下工作:

代码语言:javascript
复制
def test_a(monkeypatch):
    my_patcher(monkeypatch, patch_get_to_return_true = True, patch_get_to_return_false = False, patch_get_to_raise_exception = False)

def test_b(monkeypatch)
    my_patcher(monkeypatch, patch_get_to_return_true = True, patch_get_to_return_false = False, patch_get_to_raise_exception = False)

def test_c(monkeypatch)
    my_patcher(monkeypatch, patch_get_to_return_true = False, patch_get_to_return_false = False, patch_get_to_raise_exception = True)

def my_patcher(monkeypatch, patch_get_to_return_true = False, patch_get_to_return_false = False, patch_get_to_raise_exception = False):

    def patch_func_pos():
        return True

    patch_func_neg():
        return False

    patch_func_exception():
        raise my_exception

    if patch_get_to_return_true:
        monkeypatch.setattr(ExternalLib, 'GET', patch_func_pos)

    if patch_get_to_return_false:
        monkeypatch.setattr(ExternalLib, 'GET', patch_func_neg)

    if patch_get_to_raise_exception:
        monkeypatch.setattr(ExternalLib, 'GET', patch_func_exception)

上面的示例只有三个修补一个函数的测试。我的实际测试文件有大约20个测试,每个测试将进一步修补几个函数。

有人能建议我一个更好的处理方法吗?是否建议将monkeypatching部分移动到单独的文件中?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-07-19 01:22:57

在不了解更多细节的情况下,我建议将my_patcher拆分成几个小的fixture:

代码语言:javascript
复制
@pytest.fixture
def mocked_GET_pos(monkeypatch):
    monkeypatch.setattr(ExternalLib, 'GET', lambda: True)


@pytest.fixture
def mocked_GET_neg(monkeypatch):
    monkeypatch.setattr(ExternalLib, 'GET', lambda: False)


@pytest.fixture
def mocked_GET_raises(monkeypatch):
    def raise_():
        raise Exception()
    monkeypatch.setattr(ExternalLib, 'GET', raise_)

现在使用pytest.mark.usefixtures在测试中自动应用该装置:

代码语言:javascript
复制
@pytest.mark.usefixtures('mocked_GET_pos')
def test_GET_pos():
    assert ExternalLib.GET()


@pytest.mark.usefixtures('mocked_GET_neg')
def test_GET_neg():
    assert not ExternalLib.GET()


@pytest.mark.usefixtures('mocked_GET_raises')
def test_GET_raises():
    with pytest.raises(Exception):
        ExternalLib.GET()

然而,根据实际情况,仍有改进的空间。例如,当测试逻辑是相同的,唯一不同的是一些测试前提条件(就像您的案例中GET的不同补丁),测试或fixtures参数化通常可以节省大量代码重复。假设您有一个自己的函数在内部调用GET

代码语言:javascript
复制
# my_lib.py

def inform():
    try:
        result = ExternalLib.GET()
    except Exception:
        return 'error'
    if result:
        return 'success'
    else:
        return 'failure'

并且您希望测试无论GET的行为如何,它是否返回有效的结果:

代码语言:javascript
复制
# test_my_lib.py

def test_inform():
    assert inform() in ['success', 'failure', 'error']

使用上面的方法,您将需要复制test_inform三次,副本之间的唯一区别是使用了不同的fixture。这可以通过编写一个参数化的fixture来避免,该fixture将接受GET的多种补丁可能性

代码语言:javascript
复制
@pytest.fixture(params=[lambda: True,
                        lambda: False,
                        raise_],
                ids=['pos', 'neg', 'exception'])
def mocked_GET(request):
    monkeypatch.setattr(ExternalLib, 'GET', request.param)

现在,在对test_inform应用mocked_GET

代码语言:javascript
复制
@pytest.mark.usefixtures('mocked_GET')
def test_inform():
    assert inform() in ['success', 'failure', 'error']

您将得到三个测试:test_inform将运行三次,每个mock传递给mocked_GET参数一次。

代码语言:javascript
复制
test_inform[pos]
test_inform[neg]
test_inform[exception]

测试也可以参数化(通过pytest.mark.parametrize),如果应用正确,参数化技术可以节省大量样板代码。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51403680

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档