python3 mock不适用于所有路径

时间:2016-04-03 01:22:31

标签: python-3.x mocking patch

生产文件(production_file.py)是:

class MyError(Exception):
    pass

class MyClass:
    def __init__(self):
        self.value = None

    def set_value(self, value):
        self.value = value

    def foo(self):
        raise RuntimeError("error!")


class Caller:
    def bar(self, smth):
        obj = MyClass()
        obj.set_value(smth)

        try:
            obj.foo()
        except MyError:
            pass

        obj.set_value("str2")
        obj.foo()

测试文件(test.py):

import unittest

from unittest.mock import patch
from unittest.mock import call
from production_file import MyClass, Caller

class MyTest(unittest.TestCase):
    def test_caller(self):
        with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
            my_class_mock_obj = MyClassMock.return_value
            my_class_mock_obj.foo.side_effect = [MyError("msg"), "text"]

            caller = Caller()
            caller.bar("str1")

            calls = [call("str1"), call("str2")]

            my_class_mock_obj.set_value.assert_has_calls(calls)

if __name__ == '__main__':
    unittest.main()

以上内容有效。但是,如果我将生产类(MyError,MyClass,Caller)移动到测试文件中,并将补丁更新为:

with patch('test.MyClass', autospec=MyClass) as MyClassMock:

然后是实例方法" foo"不再嘲笑。

有人知道为什么会这样吗?

我也遇到过类似问题的一些更复杂的代码,其中生产代码位于my_package / src / production_file.py中,而测试位于my_package / tests / test_file.py中。 Python不会产生路径错误,路径是正确的,但模拟仍然不起作用。

1 个答案:

答案 0 :(得分:2)

如果您将test.py作为__main__运行,那么它不是test.MyClass __main__.MyClass,或两种情况__name__+".MyClass"

通过添加print语句,我能够确定所使用的类和修补的类是不同的:

class Caller:
    def bar(self, smth):
        print(MyClass) #lets see what we are actually making an instance of...
        obj = MyClass()
        ...

当补丁应用于正在使用的类时,您会看到如下内容:

<MagicMock name='MyClass' spec='MyClass' id='4387629656'>

但是当班级进入test.py时你会看到类似的东西:

<class '__main__.MyClass'>

表示:

  1. MyClass没有应用补丁(至少是用于测试的补丁。)
  2. 需要修补的类的名称是__main__.MyClass
  3. 然而,很可能你的......更复杂的情况&#34;因为这样的设置而无法正常工作:

    from production_file import MyClass
    
    class MyError(Exception):
        pass
    
    
    class Caller:
        def bar(self, smth):
            print(MyClass)
            obj = MyClass()
            ...
    
    class MyTest(unittest.TestCase):
        def test_caller(self):
            with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
                ...
    

    在这种情况下,production_file.MyClass正在修补,MyClass正在从production_file导入,因此正在修补正确的类,但输出仍为:

    <class 'production_file.MyClass'>
    

    这是因为Class直接导入到本地命名空间,所以当补丁应用于production_file时,本地命名空间仍然不受影响,我们可以检查补丁是否实际应用了:

    ...
    def bar(self, smth):
        print(MyClass)
        from production_file import MyClass as pf_MyClass
        print(pf_MyClass)
    ...
    
    
    #output:
    <class 'production_file.MyClass'>
    <MagicMock name='MyClass' spec='MyClass' id='4387847136'>
    

    如果是这种情况,您只需要导入模块,而不是直接导入类。然后,一旦应用了补丁,您将直接在文件中使用它:

    import production_file
    
    ...
    class Caller:
        def bar(self, smth):
            print(production_file.MyClass)
            obj = production_file.MyClass()
            ...
    
    class MyTest(unittest.TestCase):
        def test_caller(self):
            with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
                ...