Python:如何模拟类属性初始值设定项函数

Python:如何模拟类属性初始值设定项函数,python,static-members,python-mock,class-attributes,Python,Static Members,Python Mock,Class Attributes,我想模拟一个用于初始化类级(不是实例)属性的模块级函数。下面是一个简化的示例: # a.py def fn(): return 'asdf' class C: cls_var = fn() 下面是一个试图模拟a.fn()的单元测试: 我相信问题是,但我尝试了进口的两种变体,但都没有成功。我甚至尝试将import语句移动到test_mock_fn()中,以便在a.C进入作用域之前模拟的a.fn()将“存在”——不,仍然失败 如有任何见解,将不胜感激 这里实际发生的是,

我想模拟一个用于初始化类级(不是实例)属性的模块级函数。下面是一个简化的示例:

# a.py    
def fn(): 
    return 'asdf'

class C:
    cls_var = fn()
下面是一个试图模拟a.fn()的单元测试:

我相信问题是,但我尝试了进口的两种变体,但都没有成功。我甚至尝试将import语句移动到test_mock_fn()中,以便在a.C进入作用域之前模拟的a.fn()将“存在”——不,仍然失败


如有任何见解,将不胜感激

这里实际发生的是,当您实际导入模块时,
fn()
应该已经执行了。因此,在您已经计算了存储在class属性中的方法之后,mock就会出现

因此,当您尝试模拟该方法时,您尝试进行的测试已经太迟了

如果您只需在方法中添加一个print语句,您甚至可以看到这种情况:

def fn():
    print("I have run")
    return "asdf"
在您的测试模块中,当您导入
a
并在不运行测试的情况下运行时,您将看到
I have run
将出现在控制台输出中,而不从
a
模块显式运行任何内容

所以,这里有两种方法。您可以使用
PropertyMock
模拟类属性,使其符合您希望它存储的内容,如下所示:

@mock.patch('a.C.cls_var', new_callable=PropertyMock)
def test_mock_fn(self, mocked_p):
    mocked_p.return_value = '1234'

    self.assertEqual('1234', a.C.cls_var)
现在,您还必须意识到,通过这样做,您实际上仍然在运行
fn
,但通过此模拟,您现在在
cls_var
中使用您设置的
PropertyMock
持有“1234”

下面的建议(可能不太理想,因为它需要更改设计)需要修改使用class属性的原因。因为如果您实际将该类属性设置为实例属性,那么当您创建
C
的实例时,您的方法将执行,此时它将使用您的模拟

因此,您的类看起来像:

class C:
    def __init__(self):
        self.var = fn()
您的测试将如下所示:

@mock.patch('a.fn', return_value='1234')
def test_mock_fn(self, mocked_p):
    self.assertEqual('1234', a.C().var)

即使@idjaw的答案是正确的,并且正确地解释了发生了什么,我认为最简洁、简单和直接的方法是通过一个值直接修补
a.C.cls_var
属性,而不是模拟

@mock.patch('a.C.cls_var', '1234')
这就足够满足您的所有需要:仅当您必须替换行为类似于属性的属性时,使用
PropertyMock
就很有用


有关为什么您的方法不起作用的每一个细节,请查看@idjaw的答案。

您是否尝试将导入更改为使用from语句?从一个进口的fnHi Ranier-yep,试过了;不走运。(当我提到“…导入的两种变体…”时,我应该更清楚。mock上的Python文档给出了使用
import a
从import SomeClass
导入的示例。我尝试了两种口味)Hi-idjaw-嘿,我喜欢
PropertyMock
的想法!我试试看。让
fn()
运行并不理想,但值得一试。关于你的第二个想法,相信我,我很想重构这段代码。当然,我给出的例子是对真正问题的抽象,它涉及到SQLAlchemy。SQLAlchemy广泛使用了类属性,我几乎无法对其进行更改。谢谢
@mock.patch('a.C.cls_var', '1234')