【问题标题】:Patch - Why won't the relative patch target name work?补丁 - 为什么相对补丁目标名称不起作用?
【发布时间】:2013-04-10 06:08:20
【问题描述】:

我已经从一个模块中导入了一个类,但是当我尝试修补类名而不使用它的模块作为前缀时,我得到一个类型错误:

TypeError: Need a valid target to patch. You supplied: 'MyClass'

例如,下面的代码给了我上面的错误:

import unittest
from mock import Mock, MagicMock, patch
from notification.models import Channel, addChannelWithName, deleteChannelWithName, listAllChannelNames

class TestChannel(unittest.TestCase):
    @patch("Channel")
    def testAddChannelWithNamePutsChannel(self, *args):
        addChannelWithName("channel1")
        Channel.put.assert_called_with()

虽然代码的第二个版本没有给我类型错误:

import unittest
from mock import Mock, MagicMock, patch
from notification.models import Channel, addChannelWithName, deleteChannelWithName, listAllChannelNames

class TestChannel(unittest.TestCase):
    @patch("notification.models.Channel")
    def testAddChannelWithNamePutsChannel(self, *args):
        addChannelWithName("channel1")
        Channel.put.assert_called_with()

这是为什么呢?为什么我可以在其他地方将 Channel 引用为“Channel”,而对于补丁我需要模块前缀才不会出错?另外,我有一种感觉,给出完整的模块前缀也不起作用,因为当我调用 Channel.put.assert_call_with() 时,我得到一个错误,即 assert_call_with 不是 Channel.put 的属性。有人可以解释发生了什么吗?非常感谢!

【问题讨论】:

    标签: python mocking patch


    【解决方案1】:

    patch 装饰器要求目标是完整的虚线路径,如documentation 中所述:

    target 应该是“package.module.ClassName”形式的字符串。导入目标并将指定对象替换为新对象,因此目标必须可以从您调用补丁的环境中导入。目标是在执行装饰函数时导入的,而不是在装饰时。

    "Channel" 只是一个字符串,patch 没有足够的信息来找到合适的类。这与您在其他地方使用的名称 Channel 不同,后者是在模块顶部导入的。

    第二个测试失败,因为 Channel 被导入到测试模块中然后补丁用模拟对象替换了 notification.models 中的 Channel。 patch 实际所做的是更改在 notification.models 中使用的 name Channel 指向的对象。测试模块中的名称 Channel 已经定义好了,所以不受影响。这实际上在这里得到了更好的解释:https://docs.python.org/3/library/unittest.mock.html#where-to-patch

    要访问对象的修补版本,您可以直接访问模块:

    import unittest 
    from unittest.mock import patch 
    from notification.models import Channel, addChannelWithName  
    from notification import models 
                     
    class TestChannel1(unittest.TestCase): 
        @patch("notification.models.Channel") 
        def testAddChannelWithNamePutsChannel(self, *args): 
            addChannelWithName("channel1") 
            models.Channel.put.assert_called_with("channel1") 
    

    或者使用作为额外参数传递给修饰函数的修补版本:

    class TestChannel2(unittest.TestCase): 
        @patch("notification.models.Channel") 
        def testAddChannelWithNamePutsChannel(self, mock_channel): 
            addChannelWithName("channel1") 
            mock_channel.put.assert_called_with("channel1") 
    

    如果您只想快速修补对象上的单个方法,通常使用patch.object 装饰器更容易:

    class TestChannel3(unittest.TestCase):
        @patch.object(Channel, 'put')    
        def testAddChannelWithNamePutsChannel(self, *arg): 
            addChannelWithName("channel1") 
            Channel.put.assert_called_with("channel1") 
    

    【讨论】:

    • 第三个选项不仅适用于对象的方法,还适用于模拟模块的整个类——这正是我所寻找的。谢谢!
    • 如果您要模拟的对象不在其他模块中,请使用patch("__main__.my_obj")
    • 使用 @patch.object() 在搜索了几个小时后为什么 @patch() 没有救了我。它特别方便,因为您将要修补的函数/类导入到您的测试文件中,并且可以确保找到它。
    • 链接失效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 2015-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-15
    • 2016-11-15
    相关资源
    最近更新 更多