gpt4 book ai didi

Python 猴子补丁最佳实践

转载 作者:行者123 更新时间:2023-12-01 09:14:08 25 4
gpt4 key购买 nike

我正在测试一个具有多个外部依赖项的应用程序,并且我使用猴子补丁技术通过自定义实现来修补外部库的功能,以帮助我的测试。它按预期工作。

但是我目前遇到的问题是这使得我的测试文件非常困惑。我有几个测试,每个测试都需要它自己的修补函数的实现。

例如,假设我有一个来自外部库的 GET 函数,我的 test_a() 需要对 GET() 进行修补,以便它返回 False 并且test_b() 需要修补 GET() 才能返回 True。

处理这种情况的首选方法是什么?目前我执行以下操作:

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部分移动到单独的文件中?

最佳答案

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

@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在测试中自动应用 fixture :

@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 进行不同修补)时,测试或装置参数化通常会节省大量代码重复。假设您有一个自己的函数,在内部调用 GET:

# my_lib.py

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

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

# test_my_lib.py

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

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

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

现在,当将 mocked_GET 应用于 test_inform 时:

@pytest.mark.usefixtures('mocked_GET')
def test_inform():
assert inform() in ['success', 'failure', 'error']

您可以从一个测试中获得三个测试:test_inform 将运行三次,每个模拟传递给 mocked_GET 参数一次。

test_inform[pos]
test_inform[neg]
test_inform[exception]

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

关于Python 猴子补丁最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51403680/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com