Python:如何模拟类属性初始化函数

5

我想模拟一个用于初始化类级别(而不是实例)属性的模块级函数。 这里有一个简化的例子:

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

class C:
    cls_var = fn()

以下是一个尝试mock a.fn()的单元测试:

# test_a.py 
import unittest, mock
import a

class TestStuff(unittest.TestCase):
    # we want to mock a.fn so that the class variable
    # C.cls_var gets assigned the output of our mock

    @mock.patch('a.fn', return_value='1234')
    def test_mock_fn(self, mocked_fn):
        print mocked_fn(), " -- as expected, prints '1234'"
        self.assertEqual('1234', a.C.cls_var) # fails! C.cls_var is 'asdf'

我认为问题在于patch的位置,但我尝试了两种导入的变化都没有成功。我甚至尝试将导入语句移动到test_mock_fn()中,以便mocked a.fn()会在a.C进入作用域之前“存在” - 但是还是失败了。
任何见解都将不胜感激!

你尝试过将import改为from语句吗? from a import fn - Rainer
嗨,Ranier - 是的,我尝试过了;没有运气。(当我提到“...导入的两种变体...”时,我应该更清楚。Python关于mock的文档给出了使用import afrom a import SomeClass的示例。我尝试了这两种方式) - Steven Colby
2个回答

5
实际上,当您导入模块时,fn()已经执行。 因此,模拟在您已经评估存储在类属性中的方法之后才会出现。
因此,在尝试进行测试时,当您想要模拟该方法时,为时已晚。
如果您只是在方法中添加打印语句,就可以看到这种情况发生了:
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,但是通过这种模拟,您现在使用设置的PropertyMock将'1234'保存在cls_var中。
以下建议(可能不太理想,因为它需要进行设计更改)需要修改为什么要使用类属性。因为如果您实际上将该类属性设置为实例属性,那么当您创建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 - 嘿,我喜欢PropertyMock的想法!我会尝试一下。让fn()运行不是最理想的,但值得一试。关于你的第二个想法,相信我,我很想重构这段代码。但当然,我给出的示例是对涉及SQLAlchemy的实际问题的抽象。SQLAlchemy广泛使用类属性,我无法改变它。谢谢! - Steven Colby

0

即使 @idjaw 的答案是正确的并且解释得很清楚,我认为最简洁、简单和直接的方法是直接通过一个值来修补 a.C.cls_var 属性,而不是使用模拟。

@mock.patch('a.C.cls_var', '1234')

这已经足够满足你的所有需求了:只有当你必须替换一个应该像属性一样行为的属性时,使用PropertyMock才是有用的。

关于为什么你的方法不起作用的详细信息,请参阅@idjaw的答案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接