【问题标题】:Python mock patch with sub module带有子模块的 Python 模拟补丁
【发布时间】:2014-02-04 17:21:07
【问题描述】:

我无法替换调用另一个模块中的函数的简单方法。根据我对模拟的理解,您必须引用被调用的方法(在它的上下文中,而不是原始方法)。下面是我正在运行的简化版本,希望它是我需要了解模拟的简单内容。补丁是否仅用于 Class 和 Class 方法,还是我在这里做错了什么?

谢谢, 史蒂夫

myapp.models.py

from myapp.backends import get_backend
class BasicClass(models.Model):
    @staticmethod
    def basic_method()
        be = get_backend()
        print be

myapp.backends._init_.py

def get_backend():
    return 'original value'

test.py

# Referencing the import in myapp.models.basic_class 
# vs directly importing myapp.backends
# as indicated here: 
# http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch
from myapp.models import get_backend
from myapp.models.basic_class import BasicClass

class ParsersTest(TestCase):

    @patch('myapp.models.get_backend')
    def test_simplified(self, moves_backend):
        # Assertion fails
        assert get_backend is moves_backend
        # Assuming that assertion fails is why the original return value is always returned
        moves_backend.return_value = 'new return value'
        BasicClass.basic_method()

【问题讨论】:

    标签: python django unit-testing python-mock


    【解决方案1】:

    使用 mock 修补的目的是替换对模块的引用,因为它将存储在 sys.modules 中,并将其替换为对您的 mock 的引用。这意味着 修补模块中的代码 将收到对模拟对象的引用。

    在您的测试中,您使用的是get_backend。在应用装饰器之前,它是直接从 myapp.models 导入到测试模块顶部的。它没有打补丁。您的补丁已经到位,但仅适用于 myapp.models 中的代码,其中引用了那里导入的 get_backend 符号。

    我知道这很令人困惑。对我来说,这是开始使用 mock 最难的部分。如果您的测试如下所示:

    class ParsersTest(TestCase):
    
        @patch('myapp.models.get_backend')
        def test_simplified(self, moves_backend):
            from myapp.models.basic_class import BasicClass
    
            # Assertion should pass
            BasicClass.basic_method()
            moves_backend.assert_called_with()
    
            moves_backend.return_value = 'new return value'
            # As should this one (if you change the method to return instead of print)
            self.assertEqual(BasicClass.basic_method(), 'new return value')
    

    我认为你的测试会通过。这里的关键区别在于您不是直接针对 get_backend 进行测试。您正在测试一个在应用补丁后使用导入的 get_backend 的方法。

    更新

    我唯一能想到的另一件事,就是我不喜欢使用补丁作为装饰器,因为您无法控制何时应用/删除补丁并担心通过 args 获取对 mock 的引用。

    尝试上下文管理器样式:

    with mock.patch('my app.models.get_backend') as moves_backend: 
          #...
    

    其余的测试逻辑嵌套在该分支下。

    更新第二部分

    我刚刚在您的原始代码中注意到BasicClassmyapp.models.basic_class.py 中。

    如果是这种情况,您应该将补丁应用到 'myapp.models.basic_class.get_backend',因为您想要修补对 get_backend 的引用,该引用正在导入到 myapp.models.basic_class子模块中。

    【讨论】:

    • 对此伪测试进行了更新。我最初的断言是完全错误的。
    • 谢谢@Casey。我试了一下,它仍在调用原始方法。您可以推荐 Mock 的其他任何奇怪的细微差别吗?
    • 我的评论中的格式丢失了,所以我用更多细节更新了我的答案。
    • 在挖掘了我的代码之后,您最初的建议和更新后的“with mock.patch”都运行良好。
    【解决方案2】:

    我想你误会了。请记住:在 Python 中,模块和类成员(或多或少)只是简单的变量。它们可能碰巧包含一个方法,但它们仍然只是变量。当您从另一个模块导入某些内容时,它只是在导入模块上创建一个变量并将一些对象放入其中。

    装饰器@patch('myapp.models.get_backend') 只是用模拟对象替换myapp.models 上的变量。这不是你想要的。在应用补丁时,myapp.models.basic_class 已经被导入,因此它已经从myapp.models 导入了对真实方法的引用。 (换句话说,它的get_backend 变量已经包含了真正的方法。)你想替换它在myapp.models.basic_class 中实际使用的变量,如下所示:

    @patch('myapp.models.basic_class.get_backend')
    

    这将用模拟填充myapp.models.basic_class 上的get_backend 变量。因此,当您调用 BasicClass.basic_method() 时,basic_method 将在 myapp.models.basic_class.get_backend 变量内部查找并找到一个 mock。

    所以试试这个:

    from myapp.models import basic_class
    
    class ParsersTest(TestCase):
        @patch('myapp.models.basic_class.get_backend')
        def test_simplified(self, moves_backend):
            assert basic_class.get_backend is moves_backend
            moves_backend.return_value = 'new return value'
            basic_class.BasicClass.basic_method()
    

    说了这么多,小心你的测试。您不想以各种可能的方式测试所有内容。在编写此测试之前,请考虑一下该测试为您提供了什么价值。

    【讨论】:

    • 感谢 @jpmc26,您敏锐地注意到 BasicClass 的错误路径。使用更正的路径,我的测试补丁仍然正确吗?也感谢班员的详细提醒。
    • @daxiang28 假设from myapp.models.basic_class import BasicClass也是一个错字,我相信是这样。
    • @daxiang28 我知道你已经解决了你的问题,但我之前错了。 from myapp.models import get_backend 出现得太早,您的代码无法正常工作。在修补之前,它将保存对真实方法的引用。你还是应该先from myapp import models 然后assert models.get_backend is moves_backend
    • 感谢您的更新。这就是我最终所做的。 Mocks 花了我一段时间才完全理解,但最终现在一切都变得有意义了。
    • 为了清楚起见,我从没想过会在现实生活中的代码中看到特定的assert。我假设你只是把它作为一种测试来弄清楚事情是如何运作的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-12-09
    • 1970-01-01
    • 2020-11-26
    • 2013-04-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多