Pie*_*eau 1 python monkeypatching pytest
我正在尝试使用 pytest 为类方法开发一个测试,该方法从字符串列表中随机选择一个字符串。
它看起来本质上类似于下面的 givemeannumber 方法:
import os.path
from random import choice
class Bob(object):
def getssh():
return os.path.join(os.path.expanduser("~admin"), '.ssh')
def givemeanumber():
nos = [1, 2, 3, 4]
chosen = choice(nos)
return chosen
Run Code Online (Sandbox Code Playgroud)
第一种方法,getssh,在 Bob 类中只是pytest 文档中的例子
我的生产代码从数据库中获取字符串列表,然后随机选择一个。所以我希望我的测试获取字符串,然后选择第一个字符串而不是随机选择。这样我就可以针对已知字符串进行测试。
根据我的阅读,我认为我需要使用monkeypatching 来伪造随机化。
这是我到目前为止所得到的
import os.path
from random import choice
from _pytest.monkeypatch import MonkeyPatch
from bob import Bob
class Testbob(object):
monkeypatch = MonkeyPatch()
def test_getssh(self):
def mockreturn(path):
return '/abc'
Testbob.monkeypatch.setattr(os.path, 'expanduser', mockreturn)
x = Bob.getssh()
assert x == '/abc/.ssh'
def test_givemeanumber(self):
Testbob.monkeypatch.setattr('random.choice', lambda x: x[0])
z = Bob.givemeanumber()
assert z == 1
Run Code Online (Sandbox Code Playgroud)
第一个测试方法再次是来自 pytest 文档的示例(在我在测试类中使用它时稍作调整)。这工作正常。
遵循我希望使用的文档中的示例,
Testbob.monkeypatch.setattr(random, 'choice', lambda x: x[0])
但这会产生
NameError: name 'random' is not defined
如果我把它改成
Testbob.monkeypatch.setattr('random.choice', lambda x: x[0])
它走得更远,但没有发生换出:
AssertionError: assert 2 == 1
猴子补丁是适合这项工作的工具吗?如果是我哪里出错了?
问题来自 Python 中变量名称的处理方式。与其他语言的主要区别在于,没有按名称将值分配给变量;只有变量的名称绑定到对象。
这是一个超出本问题范围的更大主题,但结果如下:
当您choice
从模块导入函数时random
,您将名称绑定choice
到导入时存在的函数,并将此名称放置在bob
模块的本地命名空间中。
当您修补 时random.choice
,您将choice
模块的名称重新绑定random
到新的模拟对象。
但是,bob
模块中已经导入的名称仍然是指原始函数。因为没有人修补它。函数本身没有被修改,只是名称被替换了。
因此,Bob
该类调用原始random.choice
函数,而不是模拟的函数。
要解决此问题,您可以采用以下两种方式之一(但不能同时采用两种方式,因为它们是相互冲突的):
A: 总是random.choice()
用那个确切的全名来调用函数(即 not choice
)。而且,当然,import random
在 (not from random import ...
)之前- 与您对os.path.expanduser()
.
# bob.py
import os.path
import random
class Bob(object):
@classmethod
def getssh(cls):
return os.path.join(os.path.expanduser("~admin"), '.ssh')
@classmethod
def givemeanumber(cls):
nos = [1, 2, 3, 4]
chosen = random.choice(nos) # <== !!! NOTE HERE !!!!
return chosen
Run Code Online (Sandbox Code Playgroud)
B:修补您调用的实际函数,bob.choice()
在那种情况下是 (not random.choice()
)。
# test.py
import os.path
from _pytest.monkeypatch import MonkeyPatch
from bob import Bob
class Testbob(object):
monkeypatch = MonkeyPatch()
def test_givemeanumber(self):
Testbob.monkeypatch.setattr('bob.choice', lambda x: x[0])
z = Bob.givemeanumber()
assert z == 1
Run Code Online (Sandbox Code Playgroud)
关于未知名称的原始错误random
:如果您想要patch(random, 'choice', ...)
,那么您必须import random
-random
即将名称绑定到正在修补的模块。
当你这样做只是from random import choice
,你绑定的名称choice
,而不是random
在变量的本地命名空间。