带有子模块的Python模拟补丁

时间:2014-02-04 17:21:08

标签: python django unit-testing python-mock

我无法替换在另一个模块中调用函数的简单方法。根据我对模拟的理解,你必须引用被调用的方法(在它的上下文中,而不是原始的)。下面是我正在运行的简化版本,并希望我需要了解有关模拟的简单内容。补丁是否仅用于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 ._ 初始化 _。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()

2 个答案:

答案 0 :(得分:0)

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

在您的测试中,您正在使用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: 
      #...

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

更新第二部分

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

如果是这种情况,那么您应该将补丁应用于'myapp.models.basic_class.get_backend',因为您希望修补get_backend子模块中导入的myapp.models.basic_class的引用。

答案 1 :(得分:0)

我觉得你误解了。记住:在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')

这将使用模拟填充get_backend上的myapp.models.basic_class变量。因此,当您致电BasicClass.basic_method()时,basic_method会查看myapp.models.basic_class.get_backend变量并查找模拟内容。

所以试试这个:

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()

所有这一切,要小心你的测试。你不想以各种可能的方式测试一切。考虑一下这个测试在编写之前给你的价值。