2016-02-21 38 views
4

我想嘲笑一个模块级函数用于初始化类级(非实例)属性。这里有一个简单的例子:Python:如何模拟类属性初始化函数

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

class C: 
    cls_var = fn() 

这里是试图嘲笑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' 

我相信这个问题是where to patch但我已经试过进口两种变化,没有运气。我甚至尝试将import语句移入test_mock_fn(),以便在a.C进入作用域之前模拟的a.fn()将“存在” - nope仍然失败。

任何有识之士将不胜感激!

+0

您是否尝试更改要从语句中使用的导入? 来自进口fn – Rainer

+0

嗨Ranier - 是的,试过了;没有运气。 (当我提到'......这两个变体都是导入...'时,我应该已经更清楚了,模拟中的Python文档给出了使用'import a'和'from import SomeClass'的例子,我尝试了这两种方法) –

回答

2

这里实际发生的是,当你真正导入你的模块时,fn()已经执行完毕。所以,在你已经评估了存储在你的class属性中的方法之后,模拟就进入了。

因此,当您尝试模拟该方法时,您尝试执行的测试为时已晚。

,你甚至可以看到这种情况出现,如果你只是在你的方法添加一个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_varPropertyMock中设置了'1234'。

以下建议(可能不太理想,因为它需要设计更改)将需要修改为什么使用类属性。因为如果你真的把这个类属性设置为一个实例属性,那么当你创建一个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) 
+0

Hi idjaw - 嘿,我喜欢'PropertyMock'的想法!我会尝试。让'fn()'运行并不理想,但值得一试。关于你的第二个想法,相信我很想重构这段代码。但是,我给出的例子当然是涉及SQLAlchemy的真正问题的抽象。 SQLAlchemy广泛使用了类的属性,我很难做出改变。谢谢! –

0

即使@ idjaw的答案是正确的,正确地解释什么发生,我觉得最简洁,简单直接的方法是直接通过值来替代a.C.cls_var属性,而不是模拟。

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

这足以让你需要的一切:使用PropertyMock是有用的只是当你必须替换应该像一个属性的属性。

有关为什么你的方法不起作用的每个细节看看@ idjaw的答案。