带有子模块的 Python 模拟补丁

标签 python django unit-testing python-mock

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

谢谢, 史蒂夫

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'

测试.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()

最佳答案

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

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

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

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 获取对模拟的引用。

尝试上下文管理器风格:

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

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

更新第二部分

我刚刚在您的原始代码中注意到BasicClass位于myapp.models.basic_class.py中。

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

关于带有子模块的 Python 模拟补丁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21559157/

相关文章:

IIS下Python FastCGI - stdout写入问题

python - ZMQ 延迟与 PUB-SUB(慢订阅者)

java - 使用模拟对方法进行单元测试

xcode - 在 Xcode 单元测试中使用 @testable 时为 "No such module"

unit-testing - 最小起订量,如何使用私有(private)构造函数最小起订量对象?

python - Scapy:检查捕获的 DHCP 数据包的消息类型

python - python中枚举所有组合并返回索引的最快方法

python - 带有 docker 的 Django 不运行自定义应用程序的迁移

python - Django F() 对象和自定义保存怪异

python - 在 Django 模板中, `Context` 是一个堆栈。做什么的?