【问题标题】:python3 mock doesn't work for all pathspython3 mock 不适用于所有路径
【发布时间】:2016-07-22 16:40:06
【问题描述】:

生产文件 (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 不会产生路径错误,路径是正确的,但 mock 仍然不起作用。

【问题讨论】:

    标签: python-3.x mocking patch


    【解决方案1】:

    如果您将test.py 运行为__main__,则它不是test.MyClass,而是__main__.MyClass,或者在这两种情况下都是__name__+".MyClass"

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

    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

    但是,由于这样的设置,您的“更...复杂的情况”很可能无法正常工作:

    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:
                ...
    

    【讨论】:

    • 有趣...但是如果路径错误,为什么python不会产生错误?
    • 因为没有任何问题,它只是修补错误的地方,如果你有一个文件test.py 并且在其中你`import test`你最终会加载相同文件的两个版本不同的名称。如果您添加了if __name__ == "__main__": from test import MyClass ... 等,它将按预期工作,因为它正在修补您用于测试的类。
    • 我也遇到了更复杂的情况,我有 my_package/src/my_file.py 和 my_package/tests/test.py。在这种情况下,我的一项测试使用了补丁(“src.my_file.MyClass”),但模拟仍然不起作用。有没有办法让我调试/记录测试实际修补的内容?
    • 我的编辑是否提供了更全面的解释/分析?我从来没有真正使用过 unittest,所以我大部分时间都在黑暗中摸索,直到我感觉到我认出了一些东西,在这种情况下,这是我以前见过的与 pickling classes from __main__ 不一致的命名空间。
    • 谢谢!这是解决我的问题的方法。有趣的是,在 print(MyClass) 处,它产生了完整路径(从包名开始)。但问题已解决 - 现在必须将模拟打印出来。
    猜你喜欢
    • 2012-08-29
    • 2011-07-12
    • 2012-10-18
    • 2012-01-03
    • 2019-10-13
    • 2013-07-11
    • 2012-06-16
    • 2017-07-28
    • 1970-01-01
    相关资源
    最近更新 更多